├── .eslintrc.cjs ├── .github └── workflows │ └── tests.yml ├── .gitignore ├── LICENSE ├── README.md ├── build-wasm.sh ├── index.d.ts ├── index.js ├── karma.config.cjs ├── lib ├── argon2id.js ├── blake2b.js ├── setup.d.ts ├── setup.js └── wasm.c ├── package-lock.json ├── package.json ├── test ├── argon2id.spec.ts ├── blake2b.spec.js ├── blake2b.vectors.json └── helpers │ ├── node-loader.ts │ └── utils.js └── tsconfig.json /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "extends": [ 3 | "eslint:recommended" 4 | ], 5 | 6 | "parser": "@typescript-eslint/parser", 7 | "parserOptions": { 8 | "ecmaVersion": 9, 9 | "sourceType": "module" 10 | }, 11 | 12 | "globals": { 13 | "btoa": "readonly", 14 | "atob": "readonly", 15 | "globalThis": "readonly" 16 | }, 17 | "env": { 18 | "es6": true, 19 | "browser": true, 20 | "node": true, 21 | "mocha": true 22 | }, 23 | "plugins": [ 24 | "@typescript-eslint", 25 | "import" 26 | ], 27 | "rules": { 28 | "no-unused-vars": ["error", {"args": "none"}], 29 | "prefer-spread": "off", 30 | "no-restricted-syntax": "off", 31 | "consistent-return": "off", 32 | "object-curly-newline": "off", 33 | "prefer-template": "off", 34 | "no-plusplus": "off", 35 | "no-continue": "off", 36 | "no-bitwise": "off", 37 | "no-await-in-loop": "off", 38 | "no-sequences": "warn", 39 | "no-param-reassign": "warn", 40 | "no-return-assign": "warn", 41 | "no-else-return": ["error", { "allowElseIf": true }], 42 | "no-shadow": "off", 43 | "no-undef": "error", 44 | "arrow-body-style": "off", 45 | "space-before-function-paren": "off", 46 | "operator-linebreak": "off", 47 | "implicit-arrow-linebreak": "off", 48 | "no-underscore-dangle": "off", 49 | "import/no-unresolved": ["error", { 50 | "ignore": ["^react$", "ttag", ".data"] 51 | }], 52 | "import/prefer-default-export": "off", 53 | "import/no-extraneous-dependencies": "off", 54 | "import/no-unassigned-import": "error", 55 | "import/named": "error", 56 | "max-len": ["error", { 57 | "ignoreComments": true, 58 | "code": 130, 59 | "ignoreStrings": true, 60 | "ignoreTemplateLiterals": true, 61 | "ignoreRegExpLiterals": true 62 | }], 63 | "no-multiple-empty-lines": ["error"], 64 | "no-trailing-spaces": ["error"], 65 | "eol-last": ["error"], 66 | "padded-blocks": "off", 67 | "max-classes-per-file": "off", 68 | "no-empty": "off" 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: [main] 4 | pull_request: 5 | branches: [main] 6 | 7 | jobs: 8 | build-wasm: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v3 12 | - name: Check for cached dist folder 13 | id: cache-wasm 14 | uses: actions/cache@v3 15 | with: 16 | path: dist 17 | key: dist-${{ hashFiles('**/wasm.c', '**/build-wasm.sh') }} 18 | - name: Build wasm binaries 19 | if: steps.cache-wasm.outputs.cache-hit != 'true' 20 | uses: addnab/docker-run-action@v3 21 | with: 22 | image: emscripten/emsdk 23 | options: -v ${{ github.workspace }}:/src -e CI 24 | run: | 25 | mkdir dist && ./build-wasm.sh 26 | 27 | test-browsers-latest: 28 | runs-on: ubuntu-latest 29 | needs: build-wasm 30 | 31 | steps: 32 | - uses: actions/checkout@v3 33 | - uses: actions/setup-node@v3 34 | 35 | - name: Install dependencies 36 | run: npm ci 37 | 38 | - name: Retrieve cached dist folder 39 | uses: actions/cache/restore@v3 40 | id: cache-wasm 41 | with: 42 | path: dist 43 | key: dist-${{ hashFiles('**/wasm.c', '**/build-wasm.sh') }} 44 | # ignore cache miss, since it was taken care of the `build-wasm` step and it should never occur here 45 | 46 | - name: Install Chrome 47 | run: npx playwright install-deps chrome 48 | 49 | - name: Install Firefox 50 | run: npx playwright install-deps firefox 51 | 52 | - name: Install Webkit 53 | run: npx playwright install-deps webkit 54 | 55 | - name: Run browser tests 56 | run: npm run test-browser 57 | 58 | test-browsers-compatibility: 59 | runs-on: ubuntu-latest 60 | needs: build-wasm 61 | env: 62 | BROWSERSTACK_USERNAME: ${{ secrets.BROWSERSTACK_USERNAME }} 63 | BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} 64 | 65 | steps: 66 | - uses: actions/checkout@v3 67 | - uses: actions/setup-node@v3 68 | 69 | - name: Install dependencies 70 | run: npm ci 71 | 72 | - name: Retrieve cached dist folder 73 | uses: actions/cache/restore@v3 74 | id: cache-wasm 75 | with: 76 | path: dist 77 | key: dist-${{ hashFiles('**/wasm.c', '**/build-wasm.sh') }} 78 | # ignore cache miss, since it was taken care of the `build-wasm` step and it should never occur here 79 | 80 | - name: Run browserstack tests 81 | run: npm run test-browser -- --browsers bs_ios_14,bs_safari_13_1 82 | 83 | test-node: 84 | runs-on: ubuntu-latest 85 | needs: build-wasm 86 | 87 | steps: 88 | - uses: actions/checkout@v3 89 | - uses: actions/setup-node@v3 90 | 91 | - name: Install dependencies 92 | run: npm ci 93 | 94 | - name: Retrieve cached dist folder 95 | uses: actions/cache/restore@v3 96 | id: cache-wasm 97 | with: 98 | path: dist 99 | key: dist-${{ hashFiles('**/wasm.c', '**/build-wasm.sh') }} 100 | # ignore cache miss, since it was taken care of the `build-wasm` step and it should never occur here 101 | 102 | - name: Run node tests 103 | run: npm test -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | .vscode 4 | dist 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2022, Proton AG 2 | Copyright (c) 2017, Emil Bay github@tixz.dk (for original blake2b code from https://github.com/emilbayes/blake2b) 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Argon2id 2 | 3 | Fast, lightweight Argon2id implementation for both browser and Node: 4 | - optimized for bundle size (< 7KB minified and gizipped, with wasm inlined as base64) 5 | - SIMD support, with automatic fallback to non-SIMD binary if not supported (e.g. in Safari) 6 | - performance is comparable to or better than [argon2-browser](https://github.com/antelle/argon2-browser). 7 | 8 | We initially tried implementing a solution in pure JS (no Wasm) but the running time was unacceptable. 9 | We resorted to implement part of the module in Wasm, to take advantage of 64-bit multiplications and SIMD instructions. The Wasm binary remains small thanks to the fact that the memory is fully managed by the JS side, hence no memory management function gets included in the Wasm binary. 10 | 11 | ## Install 12 | 13 | Install from npm (compiled wasm files included): 14 | 15 | ```sh 16 | npm i argon2id 17 | ``` 18 | 19 | ## Usage 20 | 21 | With bundlers like Rollup (through [plugin-wasm](https://www.npmjs.com/package/@rollup/plugin-wasm)) or Webpack (through [wasm-loader](https://www.npmjs.com/package/wasm-loader)), that automatically translate import statements like `import wasmModule from '*.wasm'` to a loader of type `wasmModule: (instanceOptions) => WebAssembly.WebAssemblyInstantiatedSource` (either sync or async), you can simply use the default export like so: 22 | 23 | ```js 24 | import loadArgon2idWasm from 'argon2id'; 25 | 26 | const argon2id = await loadArgon2idWasm(); 27 | const hash = argon2id({ 28 | password: new Uint8Array(...), 29 | salt: crypto.getRandomValues(new Uint8Array(32)), 30 | parallelism: 4, 31 | passes: 3, 32 | memorySize: 2**16 33 | }); 34 | ``` 35 | Refer to the [Argon2 RFC](https://www.rfc-editor.org/rfc/rfc9106.html#name-parameter-choice) for details about how to pick the parameters. 36 | 37 | **Note about memory usage:** every call to `loadArgon2idWasm` will instantiate and run a separate Wasm instance, with separate memory. 38 | The used Wasm memory is cleared after each call to `argon2id`, but it isn't deallocated (this is due to Wasm limitations). 39 | Re-loading the Wasm module is thus recommended in order to free the memory if multiple `argon2id` hashes are computed and some of them require considerably more memory than the rest. 40 | 41 | ### Custom Wasm loaders 42 | 43 | The library does not require a particular toolchain. If the aforementioned bundlers are not an option, you can manually take care of setting up the Wasm modules. 44 | 45 | For instance, **in Node, the library can be used without bundlers**. You will need to pass two functions that instantiate the Wasm modules to `setupWasm` (first function is expected to take care of the SIMD binary, second one the non-SIMD one): 46 | 47 | ```js 48 | import fs from 'fs'; 49 | import setupWasm from 'argon2id/lib/setup.js'; 50 | 51 | // point to compiled binaries 52 | const SIMD_FILENAME = 'argon2id/dist/simd.wasm'; 53 | const NON_SIMD_FILENAME = 'argon2id/dist/no-simd.wasm'; 54 | 55 | const argon2id = await setupWasm( 56 | (importObject) => WebAssembly.instantiate(fs.readFileSync(SIMD_FILENAME), importObject), 57 | (importObject) => WebAssembly.instantiate(fs.readFileSync(NON_SIMD_FILENAME), importObject), 58 | ); 59 | ``` 60 | 61 | Using the same principle, for browsers you can use bundlers with simple base-64 file loaders. 62 | 63 | ## Compiling 64 | 65 | **The npm package already includes the compiled binaries.**
66 | If you fork the repo, you'll have to manually compile wasm (Docker required): 67 | ```sh 68 | npm run build 69 | ``` 70 | 71 | The resulting binaries will be under `dist/`. 72 | If you do not want to use docker, you can look into installing [emscripten](https://emscripten.org/); you'll find the compilation commands to use in `build_wasm.sh`. 73 | 74 | -------------------------------------------------------------------------------- /build-wasm.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | EMCC_COMMON_FLAGS="-O3 --no-entry -sWASM=1 lib/wasm.c -sIMPORTED_MEMORY=1 -sALLOW_MEMORY_GROWTH=1 -sMAXIMUM_MEMORY=4gb -sINITIAL_MEMORY=65mb" 4 | 5 | DOCKER_OPTIONS="docker run --rm \ 6 | -v $(pwd):/src \ 7 | -u $(id -u):$(id -g) \ 8 | emscripten/emsdk" 9 | 10 | if [[ ! -z "$CI" ]]; then 11 | # In the CI (Github Actions), we are already inside the docker container, so we can run commands directly 12 | DOCKER_OPTIONS="" 13 | fi 14 | 15 | $DOCKER_OPTIONS \ 16 | emcc $EMCC_COMMON_FLAGS -o dist/no-simd.wasm 17 | 18 | # compiling with -sWASM_BIGINT changes the output, but it does not seem to boost performance, 19 | # and it might make the binary incompatible with platforms that do not support BigInts, so for now we do not set the flat 20 | $DOCKER_OPTIONS \ 21 | emcc $EMCC_COMMON_FLAGS -msimd128 -msse2 -o dist/simd.wasm 22 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | import type { computeHash, Argon2idParams } from "./lib/setup"; 2 | 3 | /** 4 | * Setup an `argon2id` instance, by loading the Wasm module that is used under the hood. The SIMD version is used as long as the platform supports it. 5 | * The loaded module is then cached across `argon2id` calls. 6 | * NB: the used Wasm memory is cleared across runs, but not de-allocated. 7 | * Re-loading is thus recommended in order to free the memory if multiple `argon2id` hashes are computed 8 | * and some of them require considerably more memory than the rest. 9 | * @returns argon2id function 10 | */ 11 | export function loadWasm(): Promise; 12 | export default loadWasm; 13 | export type { Argon2idParams, computeHash }; 14 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import setupWasm from './lib/setup.js'; 2 | import wasmSIMD from './dist/simd.wasm'; 3 | import wasmNonSIMD from './dist/no-simd.wasm'; 4 | 5 | const loadWasm = async () => setupWasm( 6 | (instanceObject) => wasmSIMD(instanceObject), 7 | (instanceObject) => wasmNonSIMD(instanceObject), 8 | ); 9 | 10 | export default loadWasm; 11 | -------------------------------------------------------------------------------- /karma.config.cjs: -------------------------------------------------------------------------------- 1 | const { chromium, firefox, webkit } = require('playwright'); 2 | process.env.CHROME_BIN = chromium.executablePath(); 3 | process.env.FIREFOX_BIN = firefox.executablePath(); 4 | process.env.WEBKIT_HEADLESS_BIN = webkit.executablePath(); 5 | 6 | /** 7 | * Karma does not automatically serve the bundled webworker asset generated by webpack, 8 | * so we need to manually reference and expose the webpack temporary output dir. 9 | * See: https://github.com/ryanclark/karma-webpack/issues/498#issuecomment-790040818 10 | */ 11 | module.exports = function(config) { 12 | config.set({ 13 | // base path that will be used to resolve all patterns (eg. files, exclude) 14 | // basePath: '..', 15 | 16 | // frameworks to use 17 | // available frameworks: https://www.npmjs.com/search?q=keywords:karma-adapter 18 | frameworks: ['mocha', 'webpack'], 19 | 20 | plugins: [ 21 | 'karma-mocha', 22 | 'karma-chrome-launcher', 23 | 'karma-firefox-launcher', 24 | 'karma-webkit-launcher', 25 | 'karma-webpack', 26 | 'karma-mocha-reporter', 27 | 'karma-browserstack-launcher' 28 | ], 29 | 30 | // list of files / patterns to load in the browser 31 | files: [{ pattern: 'test/argon2id.spec.ts', watched: false }], // blake2b tests use Buffer 32 | 33 | // list of files / patterns to exclude 34 | exclude: [], 35 | 36 | // preprocess matching files before serving them to the browser 37 | // available preprocessors: https://www.npmjs.com/search?q=keywords:karma-preprocessor 38 | preprocessors: { 39 | 'test/argon2id.spec.ts': 'webpack' 40 | }, 41 | 42 | 43 | webpack: { 44 | resolve: { 45 | fallback: { 46 | stream: false, 47 | buffer: false, 48 | }, 49 | extensions: ['', '.ts', '.js', '.json'], 50 | }, 51 | module: { 52 | rules: [ 53 | { 54 | test: /\.wasm$/, 55 | loader: 'wasm-loader', 56 | }, 57 | { 58 | test: /\.ts$/, 59 | loader: 'ts-loader' 60 | } 61 | ], 62 | }, 63 | }, 64 | 65 | // available reporters: https://www.npmjs.com/search?q=keywords:karma-reporter 66 | reporters: ['mocha'], 67 | 68 | // web server port 69 | port: 9876, 70 | 71 | // enable / disable colors in the output (reporters and logs) 72 | colors: true, 73 | 74 | // level of logging 75 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 76 | logLevel: config.LOG_INFO, 77 | 78 | // enable / disable watching file and executing tests whenever any file changes 79 | autoWatch: false, 80 | 81 | browserDisconnectTimeout: 30000, 82 | browserNoActivityTimeout : 300000, // ms, by default 10000 83 | captureTimeout: 300000, // default is 60000 84 | 85 | browserStack: { 86 | username: process.env.BROWSERSTACK_USERNAME, 87 | accessKey: process.env.BROWSERSTACK_ACCESS_KEY, 88 | build: process.env.GITHUB_SHA, 89 | name: process.env.GITHUB_WORKFLOW, 90 | project: `argon2id/${process.env.GITHUB_EVENT_NAME || 'push'}`, 91 | retryLimit: 2 92 | }, 93 | 94 | customLaunchers: { // don't forget to pass these manually in the CI workflow 95 | bs_safari_13_1: { // no BigInt support 96 | base: 'BrowserStack', 97 | browser: 'Safari', 98 | browser_version: '13.1', 99 | os: 'OS X', 100 | os_version: 'Catalina' 101 | }, 102 | bs_ios_14: { 103 | base: 'BrowserStack', 104 | device: 'iPhone 12', 105 | real_mobile: true, 106 | os: 'ios', 107 | os_version: '14' 108 | } 109 | }, 110 | 111 | browsers: ['ChromeHeadless', 'FirefoxHeadless', 'WebkitHeadless'], 112 | 113 | client: { 114 | mocha: { 115 | timeout: 20000 116 | } 117 | }, 118 | 119 | // Continuous Integration mode 120 | // if true, Karma captures browsers, runs the tests and exits 121 | singleRun: true, 122 | 123 | // Concurrency level 124 | // how many browser instances should be started simultaneously 125 | concurrency: Infinity, 126 | }); 127 | } 128 | -------------------------------------------------------------------------------- /lib/argon2id.js: -------------------------------------------------------------------------------- 1 | import blake2b from "./blake2b.js" 2 | const TYPE = 2; // Argon2id 3 | const VERSION = 0x13; 4 | const TAGBYTES_MAX = 0xFFFFFFFF; // Math.pow(2, 32) - 1; 5 | const TAGBYTES_MIN = 4; // Math.pow(2, 32) - 1; 6 | const SALTBYTES_MAX = 0xFFFFFFFF; // Math.pow(2, 32) - 1; 7 | const SALTBYTES_MIN = 8; 8 | const passwordBYTES_MAX = 0xFFFFFFFF;// Math.pow(2, 32) - 1; 9 | const passwordBYTES_MIN = 8; 10 | const MEMBYTES_MAX = 0xFFFFFFFF;// Math.pow(2, 32) - 1; 11 | const ADBYTES_MAX = 0xFFFFFFFF; // Math.pow(2, 32) - 1; // associated data (optional) 12 | const SECRETBYTES_MAX = 32; // key (optional) 13 | 14 | const ARGON2_BLOCK_SIZE = 1024; 15 | const ARGON2_PREHASH_DIGEST_LENGTH = 64; 16 | 17 | const isLittleEndian = new Uint8Array(new Uint16Array([0xabcd]).buffer)[0] === 0xcd; 18 | 19 | // store n as a little-endian 32-bit Uint8Array inside buf (at buf[i:i+3]) 20 | function LE32(buf, n, i) { 21 | buf[i+0] = n; 22 | buf[i+1] = n >> 8; 23 | buf[i+2] = n >> 16; 24 | buf[i+3] = n >> 24; 25 | return buf; 26 | } 27 | 28 | /** 29 | * Store n as a 64-bit LE number in the given buffer (from buf[i] to buf[i+7]) 30 | * @param {Uint8Array} buf 31 | * @param {Number} n 32 | * @param {Number} i 33 | */ 34 | function LE64(buf, n, i) { 35 | if (n > Number.MAX_SAFE_INTEGER) throw new Error("LE64: large numbers unsupported"); 36 | // ECMAScript standard has engines convert numbers to 32-bit integers for bitwise operations 37 | // shifting by 32 or more bits is not supported (https://stackoverflow.com/questions/6729122/javascript-bit-shift-number-wraps) 38 | // so we manually extract each byte 39 | let remainder = n; 40 | for (let offset = i; offset < i+7; offset++) { // last byte can be ignored as it would overflow MAX_SAFE_INTEGER 41 | buf[offset] = remainder; // implicit & 0xff 42 | remainder = (remainder - buf[offset]) / 256; 43 | } 44 | return buf; 45 | } 46 | 47 | /** 48 | * Variable-Length Hash Function H' 49 | * @param {Number} outlen - T 50 | * @param {Uint8Array} X - value to hash 51 | * @param {Uint8Array} res - output buffer, of length `outlength` or larger 52 | */ 53 | function H_(outlen, X, res) { 54 | const V = new Uint8Array(64); // no need to keep around all V_i 55 | 56 | const V1_in = new Uint8Array(4 + X.length); 57 | LE32(V1_in, outlen, 0); 58 | V1_in.set(X, 4); 59 | if (outlen <= 64) { 60 | // H'^T(A) = H^T(LE32(T)||A) 61 | blake2b(outlen).update(V1_in).digest(res); 62 | return res 63 | } 64 | 65 | const r = Math.ceil(outlen / 32) - 2; 66 | 67 | // Let V_i be a 64-byte block and W_i be its first 32 bytes. 68 | // V_1 = H^(64)(LE32(T)||A) 69 | // V_2 = H^(64)(V_1) 70 | // ... 71 | // V_r = H^(64)(V_{r-1}) 72 | // V_{r+1} = H^(T-32*r)(V_{r}) 73 | // H'^T(X) = W_1 || W_2 || ... || W_r || V_{r+1} 74 | for (let i = 0; i < r; i++) { 75 | blake2b(64).update(i === 0 ? V1_in : V).digest(V); 76 | // store W_i in result buffer already 77 | res.set(V.subarray(0, 32), i*32) 78 | } 79 | // V_{r+1} 80 | const V_r1 = new Uint8Array(blake2b(outlen - 32*r).update(V).digest()); 81 | res.set(V_r1, r*32); 82 | 83 | return res; 84 | } 85 | 86 | // compute buf = xs ^ ys 87 | function XOR(wasmContext, buf, xs, ys) { 88 | wasmContext.fn.XOR( 89 | buf.byteOffset, 90 | xs.byteOffset, 91 | ys.byteOffset, 92 | ); 93 | return buf 94 | } 95 | 96 | /** 97 | * @param {Uint8Array} X (read-only) 98 | * @param {Uint8Array} Y (read-only) 99 | * @param {Uint8Array} R - output buffer 100 | * @returns 101 | */ 102 | function G(wasmContext, X, Y, R) { 103 | wasmContext.fn.G( 104 | X.byteOffset, 105 | Y.byteOffset, 106 | R.byteOffset, 107 | wasmContext.refs.gZ.byteOffset 108 | ); 109 | return R; 110 | } 111 | 112 | function G2(wasmContext, X, Y, R) { 113 | wasmContext.fn.G2( 114 | X.byteOffset, 115 | Y.byteOffset, 116 | R.byteOffset, 117 | wasmContext.refs.gZ.byteOffset 118 | ); 119 | return R; 120 | } 121 | 122 | // Generator for data-independent J1, J2. Each `next()` invocation returns a new pair of values. 123 | function* makePRNG(wasmContext, pass, lane, slice, m_, totalPasses, segmentLength, segmentOffset) { 124 | // For each segment, we do the following. First, we compute the value Z as: 125 | // Z= ( LE64(r) || LE64(l) || LE64(sl) || LE64(m') || LE64(t) || LE64(y) ) 126 | wasmContext.refs.prngTmp.fill(0); 127 | const Z = wasmContext.refs.prngTmp.subarray(0, 6 * 8); 128 | LE64(Z, pass, 0); 129 | LE64(Z, lane, 8); 130 | LE64(Z, slice, 16); 131 | LE64(Z, m_, 24); 132 | LE64(Z, totalPasses, 32); 133 | LE64(Z, TYPE, 40); 134 | 135 | // Then we compute q/(128*SL) 1024-byte values 136 | // G( ZERO(1024), 137 | // G( ZERO(1024), Z || LE64(1) || ZERO(968) ) ), 138 | // ..., 139 | // G( ZERO(1024), 140 | // G( ZERO(1024), Z || LE64(q/(128*SL)) || ZERO(968) )), 141 | for(let i = 1; i <= segmentLength; i++) { 142 | // tmp.set(Z); // no need to re-copy 143 | LE64(wasmContext.refs.prngTmp, i, Z.length); // tmp.set(ZER0968) not necessary, memory already zeroed 144 | const g2 = G2(wasmContext, wasmContext.refs.ZERO1024, wasmContext.refs.prngTmp, wasmContext.refs.prngR ); 145 | 146 | // each invocation of G^2 outputs 1024 bytes that are to be partitioned into 8-bytes values, take as X1 || X2 147 | // NB: the first generated pair must be used for the first block of the segment, and so on. 148 | // Hence, if some blocks are skipped (e.g. during the first pass), the corresponding J1J2 are discarded based on the given segmentOffset. 149 | for(let k = i === 1 ? segmentOffset*8 : 0; k < g2.length; k += 8) { 150 | yield g2.subarray(k, k+8); 151 | } 152 | } 153 | return []; 154 | } 155 | 156 | function validateParams({ type, version, tagLength, password, salt, ad, secret, parallelism, memorySize, passes }) { 157 | const assertLength = (name, value, min, max) => { 158 | if (value < min || value > max) { throw new Error(`${name} size should be between ${min} and ${max} bytes`); } 159 | } 160 | 161 | if (type !== TYPE || version !== VERSION) throw new Error('Unsupported type or version'); 162 | assertLength('password', password, passwordBYTES_MIN, passwordBYTES_MAX); 163 | assertLength('salt', salt, SALTBYTES_MIN, SALTBYTES_MAX); 164 | assertLength('tag', tagLength, TAGBYTES_MIN, TAGBYTES_MAX); 165 | assertLength('memory', memorySize, 8*parallelism, MEMBYTES_MAX); 166 | // optional fields 167 | ad && assertLength('associated data', ad, 0, ADBYTES_MAX); 168 | secret && assertLength('secret', secret, 0, SECRETBYTES_MAX); 169 | 170 | return { type, version, tagLength, password, salt, ad, secret, lanes: parallelism, memorySize, passes }; 171 | } 172 | 173 | const KB = 1024; 174 | const WASM_PAGE_SIZE = 64 * KB; 175 | 176 | export default function argon2id(params, { memory, instance: wasmInstance }) { 177 | if (!isLittleEndian) throw new Error('BigEndian system not supported'); // optmisations assume LE system 178 | 179 | const ctx = validateParams({ type: TYPE, version: VERSION, ...params }); 180 | 181 | const { G:wasmG, G2:wasmG2, xor:wasmXOR, getLZ:wasmLZ } = wasmInstance.exports; 182 | const wasmRefs = {}; 183 | const wasmFn = {}; 184 | wasmFn.G = wasmG; 185 | wasmFn.G2 = wasmG2; 186 | wasmFn.XOR = wasmXOR; 187 | 188 | // The actual number of blocks is m', which is m rounded down to the nearest multiple of 4*p. 189 | const m_ = 4 * ctx.lanes * Math.floor(ctx.memorySize / (4 * ctx.lanes)); 190 | const requiredMemory = m_ * ARGON2_BLOCK_SIZE + 10 * KB; // Additional KBs for utility references 191 | if (memory.buffer.byteLength < requiredMemory) { 192 | const missing = Math.ceil((requiredMemory - memory.buffer.byteLength) / WASM_PAGE_SIZE) 193 | // If enough memory is available, the `memory.buffer` is internally detached and the reference updated. 194 | // Otherwise, the operation fails, and the original memory can still be used. 195 | memory.grow(missing) 196 | } 197 | 198 | let offset = 0; 199 | // Init wasm memory needed in other functions 200 | wasmRefs.gZ = new Uint8Array(memory.buffer, offset, ARGON2_BLOCK_SIZE); offset+= wasmRefs.gZ.length; 201 | wasmRefs.prngR = new Uint8Array(memory.buffer, offset, ARGON2_BLOCK_SIZE); offset+=wasmRefs.prngR.length; 202 | wasmRefs.prngTmp = new Uint8Array(memory.buffer, offset, ARGON2_BLOCK_SIZE); offset+=wasmRefs.prngTmp.length; 203 | wasmRefs.ZERO1024 = new Uint8Array(memory.buffer, offset, 1024); offset+=wasmRefs.ZERO1024.length; 204 | // Init wasm memory needed locally 205 | const lz = new Uint32Array(memory.buffer, offset, 2); offset+=lz.length * Uint32Array.BYTES_PER_ELEMENT; 206 | const wasmContext = { fn: wasmFn, refs: wasmRefs }; 207 | const newBlock = new Uint8Array(memory.buffer, offset, ARGON2_BLOCK_SIZE); offset+=newBlock.length; 208 | const blockMemory = new Uint8Array(memory.buffer, offset, ctx.memorySize * ARGON2_BLOCK_SIZE); 209 | const allocatedMemory = new Uint8Array(memory.buffer, 0, offset); 210 | 211 | // 1. Establish H_0 212 | const H0 = getH0(ctx); 213 | 214 | // 2. Allocate the memory as m' 1024-byte blocks 215 | // For p lanes, the memory is organized in a matrix B[i][j] of blocks with p rows (lanes) and q = m' / p columns. 216 | const q = m_ / ctx.lanes; 217 | const B = new Array(ctx.lanes).fill(null).map(() => new Array(q)); 218 | const initBlock = (i, j) => { 219 | B[i][j] = blockMemory.subarray(i*q*1024 + j*1024, (i*q*1024 + j*1024) + ARGON2_BLOCK_SIZE); 220 | return B[i][j]; 221 | } 222 | 223 | for (let i = 0; i < ctx.lanes; i++) { 224 | // const LEi = LE0; // since p = 1 for us 225 | const tmp = new Uint8Array(H0.length + 8); 226 | // 3. Compute B[i][0] for all i ranging from (and including) 0 to (not including) p 227 | // B[i][0] = H'^(1024)(H_0 || LE32(0) || LE32(i)) 228 | tmp.set(H0); LE32(tmp, 0, H0.length); LE32(tmp, i, H0.length + 4); 229 | H_(ARGON2_BLOCK_SIZE, tmp, initBlock(i, 0)); 230 | // 4. Compute B[i][1] for all i ranging from (and including) 0 to (not including) p 231 | // B[i][1] = H'^(1024)(H_0 || LE32(1) || LE32(i)) 232 | LE32(tmp, 1, H0.length); 233 | H_(ARGON2_BLOCK_SIZE, tmp, initBlock(i, 1)); 234 | } 235 | 236 | // 5. Compute B[i][j] for all i ranging from (and including) 0 to (not including) p and for all j ranging from (and including) 2 237 | // to (not including) q. The computation MUST proceed slicewise (Section 3.4) : first, blocks from slice 0 are computed for all lanes 238 | // (in an arbitrary order of lanes), then blocks from slice 1 are computed, etc. 239 | const SL = 4; // vertical slices 240 | const segmentLength = q / SL; 241 | for (let pass = 0; pass < ctx.passes; pass++) { 242 | // The intersection of a slice and a lane is called a segment, which has a length of q/SL. Segments of the same slice can be computed in parallel 243 | for (let sl = 0; sl < SL; sl++) { 244 | const isDataIndependent = pass === 0 && sl <= 1; 245 | for (let i = 0; i < ctx.lanes; i++) { // lane 246 | // On the first slice of the first pass, blocks 0 and 1 are already filled 247 | let segmentOffset = sl === 0 && pass === 0 ? 2 : 0; 248 | // no need to generate all J1J2s, use iterator/generator that creates the value on the fly (to save memory) 249 | const PRNG = isDataIndependent ? makePRNG(wasmContext, pass, i, sl, m_, ctx.passes, segmentLength, segmentOffset) : null; 250 | for (segmentOffset; segmentOffset < segmentLength; segmentOffset++) { 251 | const j = sl * segmentLength + segmentOffset; 252 | const prevBlock = j > 0 ? B[i][j-1] : B[i][q-1]; // B[i][(j-1) mod q] 253 | 254 | // we can assume the PRNG is never done 255 | const J1J2 = isDataIndependent ? PRNG.next().value : prevBlock; // .subarray(0, 8) not required since we only pass the byteOffset to wasm 256 | // The block indices l and z are determined for each i, j differently for Argon2d, Argon2i, and Argon2id. 257 | wasmLZ(lz.byteOffset, J1J2.byteOffset, i, ctx.lanes, pass, sl, segmentOffset, SL, segmentLength) 258 | const l = lz[0]; const z = lz[1]; 259 | // for (let i = 0; i < p; i++ ) 260 | // B[i][j] = G(B[i][j-1], B[l][z]) 261 | // The block indices l and z are determined for each i, j differently for Argon2d, Argon2i, and Argon2id. 262 | if (pass === 0) initBlock(i, j); 263 | G(wasmContext, prevBlock, B[l][z], pass > 0 ? newBlock : B[i][j]); 264 | 265 | // 6. If the number of passes t is larger than 1, we repeat step 5. However, blocks are computed differently as the old value is XORed with the new one 266 | if (pass > 0) XOR(wasmContext, B[i][j], newBlock, B[i][j]) 267 | } 268 | } 269 | } 270 | } 271 | 272 | // 7. After t steps have been iterated, the final block C is computed as the XOR of the last column: 273 | // C = B[0][q-1] XOR B[1][q-1] XOR ... XOR B[p-1][q-1] 274 | const C = B[0][q-1]; 275 | for(let i = 1; i < ctx.lanes; i++) { 276 | XOR(wasmContext, C, C, B[i][q-1]) 277 | } 278 | 279 | const tag = H_(ctx.tagLength, C, new Uint8Array(ctx.tagLength)); 280 | // clear memory since the module might be cached 281 | allocatedMemory.fill(0) // clear sensitive contents 282 | memory.grow(0) // allow deallocation 283 | // 8. The output tag is computed as H'^T(C). 284 | return tag; 285 | 286 | } 287 | 288 | function getH0(ctx) { 289 | const H = blake2b(ARGON2_PREHASH_DIGEST_LENGTH); 290 | const ZERO32 = new Uint8Array(4); 291 | const params = new Uint8Array(24); 292 | LE32(params, ctx.lanes, 0); 293 | LE32(params, ctx.tagLength, 4); 294 | LE32(params, ctx.memorySize, 8); 295 | LE32(params, ctx.passes, 12); 296 | LE32(params, ctx.version, 16); 297 | LE32(params, ctx.type, 20); 298 | 299 | const toHash = [params]; 300 | if (ctx.password) { 301 | toHash.push(LE32(new Uint8Array(4), ctx.password.length, 0)) 302 | toHash.push(ctx.password) 303 | } else { 304 | toHash.push(ZERO32) // context.password.length 305 | } 306 | 307 | if (ctx.salt) { 308 | toHash.push(LE32(new Uint8Array(4), ctx.salt.length, 0)) 309 | toHash.push(ctx.salt) 310 | } else { 311 | toHash.push(ZERO32) // context.salt.length 312 | } 313 | 314 | if (ctx.secret) { 315 | toHash.push(LE32(new Uint8Array(4), ctx.secret.length, 0)) 316 | toHash.push(ctx.secret) 317 | // todo clear secret? 318 | } else { 319 | toHash.push(ZERO32) // context.secret.length 320 | } 321 | 322 | if (ctx.ad) { 323 | toHash.push(LE32(new Uint8Array(4), ctx.ad.length, 0)) 324 | toHash.push(ctx.ad) 325 | } else { 326 | toHash.push(ZERO32) // context.ad.length 327 | } 328 | H.update(concatArrays(toHash)) 329 | 330 | const outputBuffer = H.digest(); 331 | return new Uint8Array(outputBuffer); 332 | } 333 | 334 | function concatArrays(arrays) { 335 | if (arrays.length === 1) return arrays[0]; 336 | 337 | let totalLength = 0; 338 | for (let i = 0; i < arrays.length; i++) { 339 | if (!(arrays[i] instanceof Uint8Array)) { 340 | throw new Error('concatArrays: Data must be in the form of a Uint8Array'); 341 | } 342 | 343 | totalLength += arrays[i].length; 344 | } 345 | 346 | const result = new Uint8Array(totalLength); 347 | let pos = 0; 348 | arrays.forEach((element) => { 349 | result.set(element, pos); 350 | pos += element.length; 351 | }); 352 | 353 | return result; 354 | } 355 | -------------------------------------------------------------------------------- /lib/blake2b.js: -------------------------------------------------------------------------------- 1 | // Adapted from the reference implementation in RFC7693 2 | // Initial port to Javascript by https://github.com/dcposch and https://github.com/emilbayes 3 | 4 | // Uint64 values are represented using two Uint32s, stored as little endian 5 | // NB: Uint32Arrays endianness depends on the underlying system, so for interoperability, conversions between Uint8Array and Uint32Arrays 6 | // need to be manually handled 7 | 8 | // 64-bit unsigned addition (little endian, in place) 9 | // Sets a[i,i+1] += b[j,j+1] 10 | // `a` and `b` must be Uint32Array(2) 11 | function ADD64 (a, i, b, j) { 12 | a[i] += b[j]; 13 | a[i+1] += b[j+1] + (a[i] < b[j]); // add carry 14 | } 15 | 16 | // Increment 64-bit little-endian unsigned value by `c` (in place) 17 | // `a` must be Uint32Array(2) 18 | function INC64 (a, c) { 19 | a[0] += c; 20 | a[1] += (a[0] < c); 21 | } 22 | 23 | // G Mixing function 24 | // The ROTRs are inlined for speed 25 | function G (v, m, a, b, c, d, ix, iy) { 26 | ADD64(v, a, v, b) // v[a,a+1] += v[b,b+1] 27 | ADD64(v, a, m, ix) // v[a, a+1] += x ... x0 28 | 29 | // v[d,d+1] = (v[d,d+1] xor v[a,a+1]) rotated to the right by 32 bits 30 | let xor0 = v[d] ^ v[a] 31 | let xor1 = v[d + 1] ^ v[a + 1] 32 | v[d] = xor1 33 | v[d + 1] = xor0 34 | 35 | ADD64(v, c, v, d) 36 | 37 | // v[b,b+1] = (v[b,b+1] xor v[c,c+1]) rotated right by 24 bits 38 | xor0 = v[b] ^ v[c] 39 | xor1 = v[b + 1] ^ v[c + 1] 40 | v[b] = (xor0 >>> 24) ^ (xor1 << 8) 41 | v[b + 1] = (xor1 >>> 24) ^ (xor0 << 8) 42 | 43 | ADD64(v, a, v, b) 44 | ADD64(v, a, m, iy) 45 | 46 | // v[d,d+1] = (v[d,d+1] xor v[a,a+1]) rotated right by 16 bits 47 | xor0 = v[d] ^ v[a] 48 | xor1 = v[d + 1] ^ v[a + 1] 49 | v[d] = (xor0 >>> 16) ^ (xor1 << 16) 50 | v[d + 1] = (xor1 >>> 16) ^ (xor0 << 16) 51 | 52 | ADD64(v, c, v, d) 53 | 54 | // v[b,b+1] = (v[b,b+1] xor v[c,c+1]) rotated right by 63 bits 55 | xor0 = v[b] ^ v[c] 56 | xor1 = v[b + 1] ^ v[c + 1] 57 | v[b] = (xor1 >>> 31) ^ (xor0 << 1) 58 | v[b + 1] = (xor0 >>> 31) ^ (xor1 << 1) 59 | } 60 | 61 | // Initialization Vector 62 | const BLAKE2B_IV32 = new Uint32Array([ 63 | 0xF3BCC908, 0x6A09E667, 0x84CAA73B, 0xBB67AE85, 64 | 0xFE94F82B, 0x3C6EF372, 0x5F1D36F1, 0xA54FF53A, 65 | 0xADE682D1, 0x510E527F, 0x2B3E6C1F, 0x9B05688C, 66 | 0xFB41BD6B, 0x1F83D9AB, 0x137E2179, 0x5BE0CD19 67 | ]) 68 | 69 | // These are offsets into a Uint64 buffer. 70 | // Multiply them all by 2 to make them offsets into a Uint32 buffer 71 | const SIGMA = new Uint8Array([ 72 | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 73 | 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3, 74 | 11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4, 75 | 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8, 76 | 9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13, 77 | 2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9, 78 | 12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11, 79 | 13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10, 80 | 6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5, 81 | 10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0, 82 | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 83 | 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 84 | ].map(x => x * 2)) 85 | 86 | // Compression function. 'last' flag indicates last block. 87 | // Note: we're representing 16 uint64s as 32 uint32s 88 | function compress(S, last) { 89 | const v = new Uint32Array(32) 90 | const m = new Uint32Array(S.b.buffer, S.b.byteOffset, 32) 91 | 92 | // init work variables 93 | for (let i = 0; i < 16; i++) { 94 | v[i] = S.h[i] 95 | v[i + 16] = BLAKE2B_IV32[i] 96 | } 97 | 98 | // low 64 bits of offset 99 | v[24] ^= S.t0[0] 100 | v[25] ^= S.t0[1] 101 | // high 64 bits not supported (`t1`), offset may not be higher than 2**53-1 102 | 103 | // if last block 104 | const f0 = last ? 0xFFFFFFFF : 0; 105 | v[28] ^= f0; 106 | v[29] ^= f0; 107 | 108 | // twelve rounds of mixing 109 | for (let i = 0; i < 12; i++) { 110 | // ROUND(r) 111 | const i16 = i << 4; 112 | G(v, m, 0, 8, 16, 24, SIGMA[i16 + 0], SIGMA[i16 + 1]) 113 | G(v, m, 2, 10, 18, 26, SIGMA[i16 + 2], SIGMA[i16 + 3]) 114 | G(v, m, 4, 12, 20, 28, SIGMA[i16 + 4], SIGMA[i16 + 5]) 115 | G(v, m, 6, 14, 22, 30, SIGMA[i16 + 6], SIGMA[i16 + 7]) 116 | G(v, m, 0, 10, 20, 30, SIGMA[i16 + 8], SIGMA[i16 + 9]) 117 | G(v, m, 2, 12, 22, 24, SIGMA[i16 + 10], SIGMA[i16 + 11]) 118 | G(v, m, 4, 14, 16, 26, SIGMA[i16 + 12], SIGMA[i16 + 13]) 119 | G(v, m, 6, 8, 18, 28, SIGMA[i16 + 14], SIGMA[i16 + 15]) 120 | } 121 | 122 | for (let i = 0; i < 16; i++) { 123 | S.h[i] ^= v[i] ^ v[i + 16] 124 | } 125 | } 126 | 127 | // Creates a BLAKE2b hashing context 128 | // Requires an output length between 1 and 64 bytes 129 | // Takes an optional Uint8Array key 130 | class Blake2b { 131 | constructor(outlen, key, salt, personal) { 132 | const params = new Uint8Array(64) 133 | // 0: outlen, keylen, fanout, depth 134 | // 4: leaf length, sequential mode 135 | // 8: node offset 136 | // 12: node offset 137 | // 16: node depth, inner length, rfu 138 | // 20: rfu 139 | // 24: rfu 140 | // 28: rfu 141 | // 32: salt 142 | // 36: salt 143 | // 40: salt 144 | // 44: salt 145 | // 48: personal 146 | // 52: personal 147 | // 56: personal 148 | // 60: personal 149 | 150 | // init internal state 151 | this.S = { 152 | b: new Uint8Array(BLOCKBYTES), 153 | h: new Uint32Array(OUTBYTES_MAX / 4), 154 | t0: new Uint32Array(2), // input counter `t`, lower 64-bits only 155 | c: 0, // `fill`, pointer within buffer, up to `BLOCKBYTES` 156 | outlen // output length in bytes 157 | } 158 | 159 | // init parameter block 160 | params[0] = outlen 161 | if (key) params[1] = key.length 162 | params[2] = 1 // fanout 163 | params[3] = 1 // depth 164 | if (salt) params.set(salt, 32) 165 | if (personal) params.set(personal, 48) 166 | const params32 = new Uint32Array(params.buffer, params.byteOffset, params.length / Uint32Array.BYTES_PER_ELEMENT); 167 | 168 | // initialize hash state 169 | for (let i = 0; i < 16; i++) { 170 | this.S.h[i] = BLAKE2B_IV32[i] ^ params32[i]; 171 | } 172 | 173 | // key the hash, if applicable 174 | if (key) { 175 | const block = new Uint8Array(BLOCKBYTES) 176 | block.set(key) 177 | this.update(block) 178 | } 179 | } 180 | 181 | // Updates a BLAKE2b streaming hash 182 | // Requires Uint8Array (byte array) 183 | update(input) { 184 | if (!(input instanceof Uint8Array)) throw new Error('Input must be Uint8Array or Buffer') 185 | // for (let i = 0; i < input.length; i++) { 186 | // if (this.S.c === BLOCKBYTES) { // buffer full 187 | // INC64(this.S.t0, this.S.c) // add counters 188 | // compress(this.S, false) 189 | // this.S.c = 0 // empty buffer 190 | // } 191 | // this.S.b[this.S.c++] = input[i] 192 | // } 193 | let i = 0 194 | while(i < input.length) { 195 | if (this.S.c === BLOCKBYTES) { // buffer full 196 | INC64(this.S.t0, this.S.c) // add counters 197 | compress(this.S, false) 198 | this.S.c = 0 // empty buffer 199 | } 200 | let left = BLOCKBYTES - this.S.c 201 | this.S.b.set(input.subarray(i, i + left), this.S.c) // end index can be out of bounds 202 | const fill = Math.min(left, input.length - i) 203 | this.S.c += fill 204 | i += fill 205 | } 206 | return this 207 | } 208 | 209 | /** 210 | * Return a BLAKE2b hash, either filling the given Uint8Array or allocating a new one 211 | * @param {Uint8Array} [prealloc] - optional preallocated buffer 212 | * @returns {ArrayBuffer} message digest 213 | */ 214 | digest(prealloc) { 215 | INC64(this.S.t0, this.S.c) // mark last block offset 216 | 217 | // final block, padded 218 | this.S.b.fill(0, this.S.c); 219 | this.S.c = BLOCKBYTES; 220 | compress(this.S, true) 221 | 222 | const out = prealloc || new Uint8Array(this.S.outlen); 223 | for (let i = 0; i < this.S.outlen; i++) { 224 | // must be loaded individually since default Uint32 endianness is platform dependant 225 | out[i] = this.S.h[i >> 2] >> (8 * (i & 3)) 226 | } 227 | this.S.h = null; // prevent calling `update` after `digest` 228 | return out.buffer; 229 | } 230 | } 231 | 232 | 233 | export default function createHash(outlen, key, salt, personal) { 234 | if (outlen > OUTBYTES_MAX) throw new Error(`outlen must be at most ${OUTBYTES_MAX} (given: ${outlen})`) 235 | if (key) { 236 | if (!(key instanceof Uint8Array)) throw new Error('key must be Uint8Array or Buffer') 237 | if (key.length > KEYBYTES_MAX) throw new Error(`key size must be at most ${KEYBYTES_MAX} (given: ${key.length})`) 238 | } 239 | if (salt) { 240 | if (!(salt instanceof Uint8Array)) throw new Error('salt must be Uint8Array or Buffer') 241 | if (salt.length !== SALTBYTES) throw new Error(`salt must be exactly ${SALTBYTES} (given: ${salt.length}`) 242 | } 243 | if (personal) { 244 | if (!(personal instanceof Uint8Array)) throw new Error('personal must be Uint8Array or Buffer') 245 | if (personal.length !== PERSONALBYTES) throw new Error(`salt must be exactly ${PERSONALBYTES} (given: ${personal.length}`) 246 | } 247 | 248 | return new Blake2b(outlen, key, salt, personal) 249 | } 250 | 251 | const OUTBYTES_MAX = 64; 252 | const KEYBYTES_MAX = 64; 253 | const SALTBYTES = 16; 254 | const PERSONALBYTES = 16; 255 | const BLOCKBYTES = 128; 256 | 257 | -------------------------------------------------------------------------------- /lib/setup.d.ts: -------------------------------------------------------------------------------- 1 | export interface Argon2idParams { 2 | password: Uint8Array; 3 | salt: Uint8Array; 4 | /** Degree of parallelism (number of lanes) */ 5 | parallelism: number; 6 | /** Number of iterations */ 7 | passes: number; 8 | /** Memory cost in kibibytes */ 9 | memorySize: number; 10 | /** Output tag length */ 11 | tagLength: number; 12 | /** Associated Data */ 13 | ad?: Uint8Array; 14 | /** Secret Data */ 15 | secret?: Uint8Array; 16 | } 17 | 18 | declare function argon2id(params: Argon2idParams): Uint8Array; 19 | export type computeHash = typeof argon2id; 20 | 21 | type MaybePromise = T | Promise; 22 | 23 | declare function customInstanceLoader(importObject: WebAssembly.Imports): MaybePromise; 24 | 25 | /** 26 | * Load Wasm module and return argon2id wrapper. 27 | * It is platform-independent and it relies on the two functions given in input to instatiate the Wasm instances. 28 | * @param getSIMD - function instantiating and returning the SIMD Wasm instance 29 | * @param getNonSIMD - function instantiating and returning the non-SIMD Wasm instance 30 | * @returns {computeHash} 31 | */ 32 | export default function setupWasm( 33 | getSIMD: typeof customInstanceLoader, 34 | getNonSIMD: typeof customInstanceLoader, 35 | ): Promise; 36 | -------------------------------------------------------------------------------- /lib/setup.js: -------------------------------------------------------------------------------- 1 | import argon2id from "./argon2id.js"; 2 | 3 | let isSIMDSupported; 4 | async function wasmLoader(memory, getSIMD, getNonSIMD) { 5 | const importObject = { env: { memory } }; 6 | if (isSIMDSupported === undefined) { 7 | try { 8 | const loaded = await getSIMD(importObject); 9 | isSIMDSupported = true; 10 | return loaded; 11 | } catch(e) { 12 | isSIMDSupported = false; 13 | } 14 | } 15 | 16 | const loader = isSIMDSupported ? getSIMD : getNonSIMD; 17 | return loader(importObject); 18 | } 19 | 20 | export default async function setupWasm(getSIMD, getNonSIMD) { 21 | const memory = new WebAssembly.Memory({ 22 | // in pages of 64KiB each 23 | // these values need to be compatible with those declared when building in `build-wasm` 24 | initial: 1040, // 65MB 25 | maximum: 65536, // 4GB 26 | }); 27 | const wasmModule = await wasmLoader(memory, getSIMD, getNonSIMD); 28 | 29 | /** 30 | * Argon2id hash function 31 | * @callback computeHash 32 | * @param {Object} params 33 | * @param {Uint8Array} params.password - password 34 | * @param {Uint8Array} params.salt - salt 35 | * @param {Integer} params.parallelism 36 | * @param {Integer} params.passes 37 | * @param {Integer} params.memorySize - in kibibytes 38 | * @param {Integer} params.tagLength - output tag length 39 | * @param {Uint8Array} [params.ad] - associated data (optional) 40 | * @param {Uint8Array} [params.secret] - secret data (optional) 41 | * @return {Uint8Array} argon2id hash 42 | */ 43 | const computeHash = (params) => argon2id(params, { instance: wasmModule.instance, memory }); 44 | 45 | return computeHash; 46 | } 47 | -------------------------------------------------------------------------------- /lib/wasm.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Vectorised code mostly taken from: Argon2 reference C implementations (www.github.com/P-H-C/phc-winner-argon2) 3 | * Copyright 2015 Daniel Dinu, Dmitry Khovratovich, Jean-Philippe Aumasson, and Samuel Neves 4 | * Licence: CC0 1.0 Universal (https://creativecommons.org/publicdomain/zero/1.0) 5 | */ 6 | #include 7 | // #include 8 | #undef EMSCRIPTEN_KEEPALIVE 9 | #define EMSCRIPTEN_KEEPALIVE __attribute__((used)) __attribute__((retain)) 10 | 11 | #if defined(__SSSE3__) || defined(__SSE2__) 12 | #include 13 | 14 | #if defined(__SSSE3__) 15 | #include 16 | #define r16 \ 17 | (_mm_setr_epi8(2, 3, 4, 5, 6, 7, 0, 1, 10, 11, 12, 13, 14, 15, 8, 9)) 18 | #define r24 \ 19 | (_mm_setr_epi8(3, 4, 5, 6, 7, 0, 1, 2, 11, 12, 13, 14, 15, 8, 9, 10)) 20 | #define _mm_roti_epi64(x, c) \ 21 | (-(c) == 32) \ 22 | ? _mm_shuffle_epi32((x), _MM_SHUFFLE(2, 3, 0, 1)) \ 23 | : (-(c) == 24) \ 24 | ? _mm_shuffle_epi8((x), r24) \ 25 | : (-(c) == 16) \ 26 | ? _mm_shuffle_epi8((x), r16) \ 27 | : (-(c) == 63) \ 28 | ? _mm_xor_si128(_mm_srli_epi64((x), -(c)), \ 29 | _mm_add_epi64((x), (x))) \ 30 | : _mm_xor_si128(_mm_srli_epi64((x), -(c)), \ 31 | _mm_slli_epi64((x), 64 - (-(c)))) 32 | #else /* SSE2 */ 33 | #define _mm_roti_epi64(r, c) \ 34 | _mm_xor_si128(_mm_srli_epi64((r), -(c)), _mm_slli_epi64((r), 64 - (-(c)))) 35 | #endif 36 | static __m128i fBlaMka(__m128i x, __m128i y) { 37 | const __m128i z = _mm_mul_epu32(x, y); 38 | return _mm_add_epi64(_mm_add_epi64(x, y), _mm_add_epi64(z, z)); 39 | } 40 | 41 | #define GB1(A0, B0, C0, D0, A1, B1, C1, D1) \ 42 | do { \ 43 | A0 = fBlaMka(A0, B0); \ 44 | A1 = fBlaMka(A1, B1); \ 45 | \ 46 | D0 = _mm_xor_si128(D0, A0); \ 47 | D1 = _mm_xor_si128(D1, A1); \ 48 | \ 49 | D0 = _mm_roti_epi64(D0, -32); \ 50 | D1 = _mm_roti_epi64(D1, -32); \ 51 | \ 52 | C0 = fBlaMka(C0, D0); \ 53 | C1 = fBlaMka(C1, D1); \ 54 | \ 55 | B0 = _mm_xor_si128(B0, C0); \ 56 | B1 = _mm_xor_si128(B1, C1); \ 57 | \ 58 | B0 = _mm_roti_epi64(B0, -24); \ 59 | B1 = _mm_roti_epi64(B1, -24); \ 60 | } while ((void)0, 0) 61 | 62 | #define GB2(A0, B0, C0, D0, A1, B1, C1, D1) \ 63 | do { \ 64 | A0 = fBlaMka(A0, B0); \ 65 | A1 = fBlaMka(A1, B1); \ 66 | \ 67 | D0 = _mm_xor_si128(D0, A0); \ 68 | D1 = _mm_xor_si128(D1, A1); \ 69 | \ 70 | D0 = _mm_roti_epi64(D0, -16); \ 71 | D1 = _mm_roti_epi64(D1, -16); \ 72 | \ 73 | C0 = fBlaMka(C0, D0); \ 74 | C1 = fBlaMka(C1, D1); \ 75 | \ 76 | B0 = _mm_xor_si128(B0, C0); \ 77 | B1 = _mm_xor_si128(B1, C1); \ 78 | \ 79 | B0 = _mm_roti_epi64(B0, -63); \ 80 | B1 = _mm_roti_epi64(B1, -63); \ 81 | } while ((void)0, 0) 82 | 83 | #if defined(__SSSE3__) 84 | #define DIAGONALIZE(A0, B0, C0, D0, A1, B1, C1, D1) \ 85 | do { \ 86 | __m128i t0 = _mm_alignr_epi8(B1, B0, 8); \ 87 | __m128i t1 = _mm_alignr_epi8(B0, B1, 8); \ 88 | B0 = t0; \ 89 | B1 = t1; \ 90 | \ 91 | t0 = C0; \ 92 | C0 = C1; \ 93 | C1 = t0; \ 94 | \ 95 | t0 = _mm_alignr_epi8(D1, D0, 8); \ 96 | t1 = _mm_alignr_epi8(D0, D1, 8); \ 97 | D0 = t1; \ 98 | D1 = t0; \ 99 | } while ((void)0, 0) 100 | 101 | #define UNDIAGONALIZE(A0, B0, C0, D0, A1, B1, C1, D1) \ 102 | do { \ 103 | __m128i t0 = _mm_alignr_epi8(B0, B1, 8); \ 104 | __m128i t1 = _mm_alignr_epi8(B1, B0, 8); \ 105 | B0 = t0; \ 106 | B1 = t1; \ 107 | \ 108 | t0 = C0; \ 109 | C0 = C1; \ 110 | C1 = t0; \ 111 | \ 112 | t0 = _mm_alignr_epi8(D0, D1, 8); \ 113 | t1 = _mm_alignr_epi8(D1, D0, 8); \ 114 | D0 = t1; \ 115 | D1 = t0; \ 116 | } while ((void)0, 0) 117 | #else /* SSE2 */ 118 | #define DIAGONALIZE(A0, B0, C0, D0, A1, B1, C1, D1) \ 119 | do { \ 120 | __m128i t0 = D0; \ 121 | __m128i t1 = B0; \ 122 | D0 = C0; \ 123 | C0 = C1; \ 124 | C1 = D0; \ 125 | D0 = _mm_unpackhi_epi64(D1, _mm_unpacklo_epi64(t0, t0)); \ 126 | D1 = _mm_unpackhi_epi64(t0, _mm_unpacklo_epi64(D1, D1)); \ 127 | B0 = _mm_unpackhi_epi64(B0, _mm_unpacklo_epi64(B1, B1)); \ 128 | B1 = _mm_unpackhi_epi64(B1, _mm_unpacklo_epi64(t1, t1)); \ 129 | } while ((void)0, 0) 130 | 131 | #define UNDIAGONALIZE(A0, B0, C0, D0, A1, B1, C1, D1) \ 132 | do { \ 133 | __m128i t0, t1; \ 134 | t0 = C0; \ 135 | C0 = C1; \ 136 | C1 = t0; \ 137 | t0 = B0; \ 138 | t1 = D0; \ 139 | B0 = _mm_unpackhi_epi64(B1, _mm_unpacklo_epi64(B0, B0)); \ 140 | B1 = _mm_unpackhi_epi64(t0, _mm_unpacklo_epi64(B1, B1)); \ 141 | D0 = _mm_unpackhi_epi64(D0, _mm_unpacklo_epi64(D1, D1)); \ 142 | D1 = _mm_unpackhi_epi64(D1, _mm_unpacklo_epi64(t1, t1)); \ 143 | } while ((void)0, 0) 144 | #endif 145 | // BLAKE2_ROUND in reference code 146 | #define P(A0, A1, B0, B1, C0, C1, D0, D1) \ 147 | do { \ 148 | GB1(A0, B0, C0, D0, A1, B1, C1, D1); \ 149 | GB2(A0, B0, C0, D0, A1, B1, C1, D1); \ 150 | \ 151 | DIAGONALIZE(A0, B0, C0, D0, A1, B1, C1, D1); \ 152 | \ 153 | GB1(A0, B0, C0, D0, A1, B1, C1, D1); \ 154 | GB2(A0, B0, C0, D0, A1, B1, C1, D1); \ 155 | \ 156 | UNDIAGONALIZE(A0, B0, C0, D0, A1, B1, C1, D1); \ 157 | } while ((void)0, 0) 158 | 159 | 160 | EMSCRIPTEN_KEEPALIVE void xor(__m128i* out, __m128i* x, __m128i* y){ 161 | for(uint8_t i = 0; i < 64; i++) { // ARGON2_BLOCK_SIZE (1024) / 16 bytes (128bits) = 64 162 | out[i] = _mm_xor_si128(x[i], y[i]); 163 | } 164 | } 165 | 166 | // G will be given uint64_t* values by JS, which can be automatically casted to _m128i*: 167 | // see https://stackoverflow.com/questions/11034302/sse-difference-between-mm-load-store-vs-using-direct-pointer-access 168 | EMSCRIPTEN_KEEPALIVE void G(__m128i* X, __m128i* Y, __m128i* R, __m128i* Z) { 169 | for (uint8_t i = 0; i < 64; i++) { // inlined `xor` to set both R and Z 170 | R[i] = Z[i] = _mm_xor_si128(X[i], Y[i]); 171 | } 172 | 173 | for (uint8_t i = 0; i < 8; ++i) { 174 | P(Z[8 * i + 0], Z[8 * i + 1], Z[8 * i + 2], 175 | Z[8 * i + 3], Z[8 * i + 4], Z[8 * i + 5], 176 | Z[8 * i + 6], Z[8 * i + 7]); 177 | } 178 | 179 | for (uint8_t i = 0; i < 8; ++i) { 180 | P(Z[8 * 0 + i], Z[8 * 1 + i], Z[8 * 2 + i], 181 | Z[8 * 3 + i], Z[8 * 4 + i], Z[8 * 5 + i], 182 | Z[8 * 6 + i], Z[8 * 7 + i]); 183 | } 184 | 185 | xor(R, R, Z); 186 | } 187 | 188 | // G^2 189 | EMSCRIPTEN_KEEPALIVE void G2(__m128i* X, __m128i* Y, __m128i* R, __m128i* Z) { 190 | G( X, Y, R, Z ); 191 | G( X, R, R, Z ); 192 | } 193 | 194 | #else // no vectorization 195 | 196 | uint64_t rotr64(uint64_t x, uint64_t n) { return (x >> n) ^ (x << (64 - n)); } 197 | 198 | #define LSB(x) ((x) & 0xffffffff) 199 | EMSCRIPTEN_KEEPALIVE void xor(uint64_t* out, uint64_t* x, uint64_t* y){for(uint8_t i = 0; i < 128; i++) out[i] = x[i] ^ y[i];} 200 | 201 | 202 | void GB(uint64_t* v, int a, int b, int c, int d) { 203 | 204 | // a = (a + b + 2 * trunc(a) * trunc(b)) mod 2^(64) 205 | v[a] += v[b] + 2 * LSB(v[a]) * LSB(v[b]); 206 | 207 | // d = (d XOR a) >>> 32, where >>> is a rotation 208 | v[d] = rotr64(v[d] ^ v[a], 32); 209 | // c = (c + d + 2 * trunc(c) * trunc(d)) mod 2^(64) 210 | v[c] += v[d] + 2 * LSB(v[c]) * LSB(v[d]); 211 | 212 | // b = (b XOR c) >>> 24 213 | v[b] = rotr64(v[b] ^ v[c], 24); 214 | 215 | // a = (a + b + 2 * trunc(a) * trunc(b)) mod 2^(64) 216 | v[a] += v[b] + 2 * LSB(v[a]) * LSB(v[b]); 217 | 218 | 219 | // d = (d XOR a) >>> 16 220 | v[d] = rotr64(v[d] ^ v[a], 16); 221 | 222 | 223 | // c = (c + d + 2 * trunc(c) * trunc(d)) mod 2^(64) 224 | v[c] += v[d] + 2 * LSB(v[c]) * LSB(v[d]); 225 | 226 | 227 | 228 | // b = (b XOR c) >>> 63 229 | v[b] = rotr64(v[b] ^ v[c], 63); 230 | 231 | } 232 | 233 | void P(uint64_t* v, uint16_t i0,uint16_t i1,uint16_t i2,uint16_t i3,uint16_t i4,uint16_t i5,uint16_t i6,uint16_t i7, uint16_t i8,uint16_t i9,uint16_t i10,uint16_t i11,uint16_t i12,uint16_t i13,uint16_t i14,uint16_t i15) { 234 | // v stores 16 64-bit values 235 | 236 | GB(v, i0, i4, i8, i12); 237 | GB(v, i1, i5, i9, i13); 238 | GB(v, i2, i6, i10, i14); 239 | GB(v, i3, i7, i11, i15); 240 | 241 | GB(v, i0, i5, i10, i15); 242 | GB(v, i1, i6, i11, i12); 243 | GB(v, i2, i7, i8, i13); 244 | GB(v, i3, i4, i9, i14); 245 | } 246 | // given a copy of R, compute Z (in-place) 247 | EMSCRIPTEN_KEEPALIVE void G(uint64_t* X, uint64_t* Y, uint64_t* R, uint64_t* Z) { 248 | xor(R, X, Y); 249 | // // we need to store S_i = (v_{2*i+1} || v_{2*i}), for v[i] of 64 bits 250 | // // S[0] = R[8:15] || R[0:7] 251 | for(uint8_t i = 0; i < 128; i+=16) { 252 | Z[i+0] = R[i+0]; Z[i+1] = R[i+1]; Z[i+2] = R[i+2]; Z[i+3] = R[i+3]; 253 | Z[i+4] = R[i+4]; Z[i+5] = R[i+5]; Z[i+6] = R[i+6]; Z[i+7] = R[i+7]; 254 | Z[i+8] = R[i+8]; Z[i+9] = R[i+9]; Z[i+10] = R[i+10]; Z[i+11] = R[i+11]; 255 | Z[i+12] = R[i+12]; Z[i+13] = R[i+13]; Z[i+14] = R[i+14]; Z[i+15] = R[i+15]; 256 | // const ids = [0, 1, 2, 3, 4, 5, 6, 7].map(j => i*128 + j*16); // 0, 16, .. 112 | 128, 144... 257 | // ( Q_0, Q_1, Q_2, ... , Q_7) <- P( R_0, R_1, R_2, ... , R_7) of 16-bytes each 258 | P(Z,i+0, i+1, i+2, i+3, 259 | i+4, i+5, i+6, i+7, 260 | i+8, i+9, i+10, i+11, 261 | i+12, i+13, i+14, i+15); 262 | } 263 | 264 | for(uint8_t i = 0; i < 16; i+=2) { 265 | // Q_0 = Q[8:15] || Q[0:7] 266 | // const ids = [0, 1, 2, 3, 4, 5, 6, 7].map(j => i*16 + j*128); // 128 .. 896 | 16, 144 .. 912 | .. 267 | // ( Z_0, Z_8, Z_16, ... , Z_56) <- P( Q_0, Q_8, Q_16, ... , Q_56) of 16-bytes each 268 | // ( Z_1, Z_9, Z_17, ... , Z_57) <- P( Q_1, Q_9, Q_17, ... , Q_57) ... 269 | P(Z, i+0, i+1, i+16, i+17, 270 | i+32, i+33, i+48, i+49, 271 | i+64, i+65, i+80, i+81, 272 | i+96, i+97, i+112, i+113); // store one column of Z at a time 273 | } 274 | 275 | xor(R, R, Z); 276 | 277 | } 278 | 279 | // G^2 280 | EMSCRIPTEN_KEEPALIVE void G2(uint64_t* X, uint64_t* Y, uint64_t* R, uint64_t* Z) { 281 | G( X, Y, R, Z ); 282 | G( X, R, R, Z ); 283 | } 284 | #endif 285 | 286 | // Returns out = [l, z] 287 | EMSCRIPTEN_KEEPALIVE uint32_t* getLZ(uint32_t* out, uint32_t* J1J2, uint32_t currentLane, uint32_t p, uint32_t pass, uint32_t slice, uint32_t segmentOffset, uint32_t SL, uint32_t segmentLength) { 288 | // For the first pass (r=0) and the first slice (sl=0), the block is taken from the current lane. 289 | uint32_t l = (pass == 0 && slice == 0) ? currentLane : J1J2[1] % p; 290 | 291 | // W includes the indices of all blocks in the last SL - 1 = 3 segments computed and finished (possibly from previous pass, if any). 292 | // Plus, if `l` is on the current lane, we can also reference the finished blocks in the current segment (up to 'offset') 293 | uint32_t offset = l == currentLane 294 | ? segmentOffset - 1 295 | : segmentOffset == 0 ? -1 : 0; // If B[i][j] is the first block of a segment, then the very last index from W is excluded. 296 | uint32_t segmentCount = pass == 0 ? slice : SL-1; 297 | uint64_t W_area = segmentCount * segmentLength + offset; 298 | // cast to uint64_t since we don't want the multiplication to be in uint32_t space 299 | uint32_t x = ((uint64_t)J1J2[0] * J1J2[0]) >> 32; 300 | uint32_t y = (W_area * x) >> 32; 301 | uint32_t zz = W_area - 1 - y; 302 | uint32_t startPos = pass == 0 ? 0 : (slice + 1) * segmentLength; // next segment (except for first pass) 303 | // TODO (?) possible optimisation: zz < 2 * (SL * segmentLength) so we can use an if instead of % 304 | uint32_t z = (startPos + zz) % (SL * segmentLength); 305 | out[0] = l; 306 | out[1] = z; 307 | return out; 308 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "argon2id", 3 | "version": "1.0.1", 4 | "description": "Argon2id implementation in pure Javascript", 5 | "main": "index.js", 6 | "type": "module", 7 | "types": "index.d.ts", 8 | "files": [ 9 | "dist/", 10 | "lib/", 11 | "index.d.ts" 12 | ], 13 | "scripts": { 14 | "test": "mocha --loader=ts-node/esm test/blake2b.spec.js test/argon2id.spec.ts", 15 | "lint": "eslint index.js lib test", 16 | "build": "rm -rf dist && mkdir dist && ./build-wasm.sh", 17 | "test-browser": "karma start karma.config.cjs", 18 | "preversion": "npm run build && npm test" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "git+https://github.com/openpgpjs/argon2id.git" 23 | }, 24 | "keywords": [ 25 | "argon2", 26 | "argon2id", 27 | "rfc9106" 28 | ], 29 | "license": "MIT", 30 | "devDependencies": { 31 | "@types/chai": "^4.3.4", 32 | "@types/mocha": "^10.0.1", 33 | "@typescript-eslint/eslint-plugin": "^5.51.0", 34 | "@typescript-eslint/parser": "^5.51.0", 35 | "chai": "^4.3.7", 36 | "eslint": "^8.32.0", 37 | "eslint-plugin-import": "^2.27.5", 38 | "karma": "^6.4.1", 39 | "karma-browserstack-launcher": "^1.6.0", 40 | "karma-chrome-launcher": "^3.1.1", 41 | "karma-firefox-launcher": "^2.1.2", 42 | "karma-mocha": "^2.0.1", 43 | "karma-mocha-reporter": "^2.2.5", 44 | "karma-webkit-launcher": "^2.1.0", 45 | "karma-webpack": "^5.0.0", 46 | "mocha": "^10.2.0", 47 | "playwright": "^1.30.0", 48 | "string-replace-loader": "^3.1.0", 49 | "ts-loader": "^9.4.2", 50 | "ts-node": "^10.9.1", 51 | "wasm-loader": "^1.3.0", 52 | "webpack": "^5.75.0", 53 | "webpack-cli": "^5.0.1" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /test/argon2id.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { isNode, hexToUint8Array, uint8ArrayToHex } from './helpers/utils.js'; 3 | import type { computeHash } from '../index'; 4 | 5 | describe('argon2id tests', () => { 6 | let argon2id: computeHash; 7 | 8 | before('load wasm', async function () { 9 | // Node tests do not use a bundler, so we need an alternative entry-point 10 | // @ts-ignore 11 | const { default: loadWasm } = isNode ? await import(/* webpackIgnore: true */ './helpers/node-loader.ts') : await import('../index.js'); 12 | argon2id = await loadWasm(); 13 | 14 | }); 15 | 16 | it('Test vector, 3 passes', function () { 17 | const expected = '0d640df58d78766c08c037a34a8b53c9d01ef0452d75b65eb52520e96b01e659'; 18 | const tagT3 = argon2id({ 19 | password: hexToUint8Array('0101010101010101010101010101010101010101010101010101010101010101'), 20 | salt: hexToUint8Array('02020202020202020202020202020202'), 21 | passes: 3, memorySize:32, parallelism: 4, 22 | secret: hexToUint8Array('0303030303030303'), 23 | ad: hexToUint8Array('040404040404040404040404'), 24 | tagLength: 32 25 | }); 26 | expect(uint8ArrayToHex(tagT3)).to.equal(expected); 27 | }); 28 | 29 | it('Test vector, segment length above 2', function () { 30 | // The first two columns of the first pass & slice are treated differently, and do not rely on any J1, J2. 31 | // But the corresponding pseudo-random values should still be generated, and discarded. 32 | // This test checks that the following columns use the expected pseudo-random J1 and J2. 33 | const expected = '47c71919daf18f9d1756391f1f9f4a7df3aa9608128965f1e84c0d6fcc34db87'; 34 | const tag = argon2id({ 35 | password: hexToUint8Array('0101010101010101010101010101010101010101010101010101010101010101'), 36 | salt: hexToUint8Array('02020202020202020202020202020202'), 37 | passes: 3, memorySize:32, parallelism: 2, 38 | secret: hexToUint8Array('0303030303030303'), 39 | ad: hexToUint8Array('040404040404040404040404'), 40 | tagLength: 32 41 | }); 42 | expect(uint8ArrayToHex(tag)).to.equal(expected); 43 | }); 44 | 45 | it('Test lowest recommended settings', function () { 46 | const expected = '6904f1422410f8360c6538300210a2868f5e80cd88606ec7d6e7e93b49983cea'; 47 | const tag = argon2id({ 48 | password: hexToUint8Array('0101010101010101010101010101010101010101010101010101010101010101'), 49 | salt: hexToUint8Array('0202020202020202020202020202020202020202020202020202020202020202'), 50 | passes: 3, memorySize: Math.pow(2, 16), parallelism: 4, tagLength: 32 51 | }); 52 | expect(uint8ArrayToHex(tag)).to.equal(expected); 53 | }); 54 | 55 | it('Test growing memory', function () { 56 | const expected = 'a829d4355e2d11c9514fe278ee75ed1f44a754aafdc6fbfdb01242ab3008cca6'; 57 | const tag = argon2id({ 58 | password: hexToUint8Array('0101010101010101010101010101010101010101010101010101010101010101'), 59 | salt: hexToUint8Array('0202020202020202020202020202020202020202020202020202020202020202'), 60 | passes: 3, memorySize: Math.pow(2, 17), parallelism: 4, tagLength: 32 61 | }); 62 | 63 | expect(uint8ArrayToHex(tag)).to.equal(expected); 64 | }); 65 | }) 66 | 67 | 68 | // TODO G tests: would need to init wasm memory separately. Consider removing the tests altogether, since they only matter for debugging 69 | // test('compression function and permutation', function (assert) { 70 | // const X1 = hexToUint8Array('00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'); 71 | // const Y1 = hexToUint8Array('00000000000000000000000000000000000000000000000018000000000000000300000000000000020000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000') 72 | // const expectedR1 = '71de2ae60b94a00d8fc8492472fca334c65eaa2e99bf996e18022ba8a06b72b5c3c9494a4690642d940f7907b70e8fcd85cb7251a617c78637fceda26fa5c3bc90fe016ee84b6ae3fc4ac69b9a2c0d1c2e4646098c7afb0ee13f10fcae4e1094d1c6272941ebf4ae6c7ea4584bef237d60444d75850dfffa7f77c70e121a895b02f6e3f6b873e9a4c1bd41f773dd4900ebc120abd2a6921d65a85d2bae3bad1d2461495cd1e6d88dd3e85c277a8847bdb88588b09cb0aefb416b5d77e66c8350053950a6f11234392de4de75d2352b54282e8a146c5b024d66ef8353f876c2376f18ee977a05301d9fee9da20179e86e2a8c3a448f64945f4158aa2cd81b3f28a8f858c882802f80f30402194845ae7be76dc16d3b7ead50a37b33de42307a2946311c825b532b14aa14e4a2fc1d4ad0ab50befc760597bed2a431c66c80400fc1fd7d08647de62258b95706f4ffa64f43ff1d6daad15805925abd3ff69a3d0caa19c355ba6329d5c4b6ceb28bf6d054520568bc5a3e0778cde85a87224cfea296bf7305ebf7c0ede9641254f704d04d1a46110a291baa3452be801bfa0eca33615ab766697eecf0031ee813e3564f0cf85abc6e6f34dc7a0108bd3a9423573cc7a8a496f949799812e03b9ae816e9842932f871816b95f65691d7e14879be9ab4dbea30963b9002d95205d407e9112100ab423c353abb23f6083fab4b49586b76c4ea5cc3710102be20d53891f6edffc29ee0a6afa17704c4d8a979a0d12bd3256ebdd5a04dff0bf13a4d583368f6cdb801d6ad56218565f6bd91c4845943c80cc94deb8599407b3a7a723516f71bdd83e66164c8675d00736ca5b625f753e403b1ec7df9f1b88d99b719ef7299d92f5108a5912a40386a94ba3036b65122ce151b30fb34ed8c3bcb82fd8baa5d0d86bce9a2d84c79bfb1aa92fc68c78da01b839b800be6ce3e35b834e7b94881308385da93c9c689645627fd67ab02346a9df3e690106dd5ff15af861ab07a6fe6b651591b0b7f679912ecf9ee5ed8fbe45d2a08640d27413832b421a5ec1040a8b026d90ca45f633b8513518319bf7f9babb9242fb8aa6ba925854fbad1576fb5442a0f919f9fe680f78c3eeac8d606c0c0028f2eb285b128493b07b5e0071677fa4df9bf9cdf8919f80868308594205908bc574864769a3790f2fa3291ba67ad83b6044e5cf68caf911825baf290a9dc471536073fd7a6319ebacbd70f604460057d9d16b80efe9d3105683853255f67bf6344882415c2c39095bd06246897f18628f51ce07165fae310fa424af03feae48d2fd7efd4781f1c46f594d45a35b1dfdf7a949c0a9db80e0ec2feb7fdff27097cc4357cb58be2c1f7f4e718ee735467e8b4fc4fcade44a7fbfe7ad9431ef45c9aa4ca916dd2df73985c38429e00e346ead31481bd6b77eafc95a5ca19009a26'; 73 | 74 | // assert.equals(uint8ArrayToHex(G(X1, Y1)), expectedR1); 75 | 76 | // const X2 = hexToUint8Array('c789bf4dbb447c80c686f82dc9d71495bd4f2306c9c1ef73541ff4e53286253ed9555624106fc5bb1c1f13ab85510a81453f4e465610c1099c29a7a295ebd090084ceec9d396d202b72e09c88d996500ad48c7174c8af7ca39d459e0741b709d8549f9e60f20326e49ec7d52e778b61014bdac20707546f2c385edb882fb55eba7cef30af2daec1c92f67d492fe47d65031be2619e8356656cd3a92c3076417ee593f9467d85f613595bdfa6effa40fd25717cf6953f63be4bc57270d181a808d276176cd0c31586a2e64c8664da8aede76d7fd9c39a49a46697b694c1cb47a9d9133f134ecb6460cb9915104766f5d77a9a024b8d8467e098a7c060d5fb4f31f76b2e6128243714911e504a1768dd49b386d3b2db02d76211aa7c19d0cd02055ecb03a1dac8a32617aa16ea0b441e36f28d65077005ad93f04243fb994f94c268047a3ef68ee78aea52913dcd3dca14e3b551e733d8602844e2370981217bfd022c6932cea115f873ce033f223171eef5b861e474a4c3ca9901611aeba05d531c4937e0a4e24de5c3a2ecf3d77a973e2f76c8b9efe41e101f56a0382c590f4f65a96a95c0dbdf8d8203ff74bb59f13c1cbb583843f6956f042cb49405cfc09f5ca59577a912da3c1f8b4c1d7dc7bd867fd473987f23f47ea3008580f200c72aa282e6939c0d7680ef9c4e538642c27a48ab45f928d3e95f3592abefdf56cf6ee5ecd417e74c64934287e4f586690ba455aba66b38945b4feb8293d317ae42dec602a54278be6416c123534c8cc267601d4aeae10f6066fb5b3407c9da2e61a2c15d80b1e5d5c8843fdc7762621b6d58655d555f158ac0ebfc9a72933f8699d4f49840bd67f195a882d0e5282fde6e66e481bbfbfdc5656d504d89635078735407daf75ae3fbb48eda977d1a9efadcba748672f6e9ca0b8e111ac01a5b93183f0deb8ba0edd715724a5318f7a686356011f9105cca9c579d087b4ed71dfc31f751cadfe1d63e6c02e7937da5657db0f5bae454bdfdaf1c21023e861c563999dae61a7ac557d20de99ef2f23bde7dbc609f91c9b83e2a0eec47114fc3940ae348ae79dad41a070df03637f009efe97ee6185e9f62a1e753e6c6387c0fea901c68edebefbfc1e12b2cf000202ff8e0f91881189d2597888d32a189250346d82dc0ea9309866c6ab70bc98dfd19471796158c5c8b128c3979915ad59495503720b07bf27d24c4e63ca98d49170dfab60caeea71065c189fb8764c48f02f42048b03857708ee358c8044f5d410cab23bb7acf7bdd9a8fbf7c6008306eaecbf627d2dea64549e6e1c4620dacda150e9a08e8e4d47798baabb39eaf08a663b6937a77d54b26d3f55ac0fd8b32432bb9a5543845ee428abe6f74553879c63dae1d579435cda710c245ac6a4e60bef36c05814ffc383226f5cbee8641d0c4f1afd5bd6eb'); 77 | // const Y2 = hexToUint8Array('dce6501678b0723e5841c4f37b778ebf698387885b11f20fcdf69d302277c7060eb9ddaf64cf59a7dbb4fe51043eea23c6bda8a3eae6aeba30527ae39852bd1c1812e91953d77cfcbd3c380cf6b203ce51e493f60b82ab5ec8411a22d37bd981dceb51ecf021227a9303356b1bfc25f28ebe38f25f4c34450c7931d7ae6336651d4589257be70833fbcd9063485c8cbf17cfcee71aca0d284e18aaebdc692b530e0cd535786bfd14a889354c68f947f8436c9ef662b3d1c8bb731ada8119e021f95cb410a74e5d224b3ba0488ec0ac5ab7b5581082238b955b5ce9d4f36d0608c99779967dfe79b8e9645f7a7bea8aa4f2ab67385bdb5924cf4f02a7b6da558c1f2ff4a58931cd26d59aca0f0391f19ce09d317cade5152c6607cf47a577e65ddb7ae4b7d39292ae32002275219224e2ff24933def85c008a7120f7775af8aa788a385ed7e54ec469c4a1f15bb5167ea873306094ce9433e4ed969ab87ed1cd83deb347950119360125df46aec5ad19926d00c123dc8c57c1819c225f97aee0f6a94ceab4b809ed83e99a081ea86d89d4563a426faedd2b7d48295d55d8b9baae189d9496b0602e9b43541abf2f0f0d509b3abf50116f00dbee172d8f85d470c4bcdc8cb25b092a41cf5320cc1c2fafaa272baed3b28c9bbd29ba9120bfda5adeb2fbfdaf374918aec1d7ec6c11c7cdcb7ff0421869859ea1eae42e402d978fded8a404d0940a1969692af0c5cae5ab22e8e9b7d5c4d6d4c0706c78cda0643e035739d1f1180c47794a7d4c667bd87560b30f8d18c4dde248c3d149abc1869dd57587404b0e3e0e6677085a9069d7889e7ee946aad0c571f550d9bc0dc30546cb285ae46971e58188c15f7c82600be6dea9c161cfa02e2d9693a0aa41219518e10f21fa730b47905ebccc62a294087e08ca2b47d11164cf8cf709bec087d69d895038eff8aad1ddedf0361d47c204fb4befdde8da0b2f0ec0de5d678a82b6e4c52db807964a835662c194e5ef6c25ec91cd558be0233be543a44801dd461b0a25188dfbad792e4613c9823890abae755e226679c384c02d82ad50e49c20413db3a935f7c0bd2f5ffe98b8a3df830cb519a8883803f163f2a89be05bee5d7dc876ced880ca2f726a6025c2fb9aada36c023c98f94f072a5b77107c2dace470000c513422ac1aa4d074fc80821c0eecd6213e434566478119c7b479873d3d080a1f44763d4ebb8a871f8aaa1ed7bf8aea843f773ac1e0c5d76bb7481090615368b2a6360e660f6429f224218383c5cb0ac6bc90919e264d3f883340a9f4fe9e368011790d188d3031c5e2b983f21223b3adbf5eb13bf28bb4b8c331e0838a03c4ee3dc034c2d91278c1577a746c0984a1b0d78966c5499814440cd0feafa10d9933f104b86323dbbd4ffbe3610ef98dcd15521f566a4fe6e702b06a723aeb23064') 78 | // const expectedR2 = '177138833ebd4b1361fc0bac5f480037ff179c701a4507e75feb082aed982dcdfa50289d7f6dd333b963104f34fb92b79686ecbacfdadf96a5e8bd1cf735d9f7d0881bf09df0d3954c828e64f355e07588d106391f2913d0ea8ffae87a324ecdc036cc0fc24837268521c9c71d742cfa2a05c8da80d65ea8befd45be7767d5df9af572b7c8bebdc9937543e32fc1d21e563ac5859b05fee625970beb53aa65049add0acc2b9cb00c08c273461ef9006c34b8befdc63c302a60a36f735149e780d99fef1461679fe1c33b0f814e6f065fe4f06812215e010e08d4c85f295c42c17f467f8fbad82e9818cfa03fddde7d9146155c0da40628d04d8879d6142d6e81af620b3aee03ead1f584334a80643878ef5fc4532aa15451e5c97a7edbeb3bdcf6c39050edc7bfdf50362d8fe7ed527ffda82857f2f20d39bf0f8074feeaf0ec47671eaa7f6659f07ab59c76b6019d495dc2d235ee5b5838d8aedd8b6da4d83ee43a8d3264d07c19123ec154ce7d67530563033277d3cf2a512964d35decc250d1a515063449c6834d7e6e3c116ba13ab033d43ec352f827fab48ed36b99ceaf89ff4bd25dae3f2bfcccf7d44178bbde6cfb089c95bab66ac6229b0f727f97349cd8c9b43c38b592219fb501758b2bff5009123d53196620756c980e73e707157557a02884ddd036f8ee616830120d8440c8dc02958c58c705a7c0fd5e5d8b6ece05d5f54deb965469ab8e0d28bc08d9bdac1d58e21083eefc6f9122a67f4bf0096107fd332b3a29a80b1b860b584e64b3290123493df5af5a6b94610d42bfec4aeb841cbb621bed74556e258c0a0e6cd9e08db85e17b81dd0cea8b38717026c3391ed907654f0e0fd6d3200f3ae7cbd644fe646e2c3d4430351916cb68b2e29597a25cbb9a4f6f4ac0b2ffe8c3da2190be2f35aa81f36e6aeab885d8584c4e1b9de00f850d014e6cc4a4c4a29d027e5ee5099a3297d94c3cf094cb5ae607d928e19f51b3cd31c789343ca9cc1154f814c77ca1fe43e5ef5a020edf77365b580d2e86b5352970cd8039966d21accfcff596347b446b2a111e65dc76a67f5d0ba0064ddae5efa890b9ef4f41fbbf51e8d5a9940be4ddfdf998acd7a3228be9d42248a095dec93de912c2ba68d4fee1fb97e022e21c2978d8822f4fc8cdebc6e4444a8bf93934346cfac48ceb0fcda344368793a880c14b534a304d83c0a69a700184c283cf251ca56962d467df899e1399bd6150cb4318e214ac7ce57a41bb5b0a9e0f66e88cf794243ff62e80df69e067cf02a4b636065a374031e890784767733c87c1b937535d34666bf856a967813d034b70af3f0f9d15353d0fa437983cafd3cc09159ad1db08db5d2df3d1fd83d5f4076d93b9d32266e38c67374e0d2d648ca717cc6d9664cd56bbdadd2e135d987058162352810eba176f24d7a10e12b'; 79 | 80 | // assert.equals(uint8ArrayToHex(G(X2, Y2)), expectedR2); 81 | 82 | // assert.end() 83 | // });' 84 | -------------------------------------------------------------------------------- /test/blake2b.spec.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import blake2b from '../lib/blake2b.js'; 3 | import vectors from './blake2b.vectors.json' assert { type: "json" }; 4 | import { hexToUint8Array } from './helpers/utils.js'; 5 | 6 | describe('blake2b tests', () => { 7 | describe('vectors', function () { 8 | vectors.forEach(function (v, i) { 9 | it(`vector ${i}`, () => { 10 | const input = hexToUint8Array(v.input) 11 | const key = v.key && hexToUint8Array(v.key) 12 | const salt = v.salt && hexToUint8Array(v.salt) 13 | const personal = v.personal && hexToUint8Array(v.personal) 14 | 15 | const expected = hexToUint8Array(v.out) 16 | const actual = new Uint8Array( 17 | blake2b(v.outlen, key, salt, personal, true) 18 | .update(input) 19 | .digest() 20 | ) 21 | expect(actual).to.deep.equal(expected); 22 | }); 23 | }) 24 | }); 25 | 26 | it('works with buffers', function () { 27 | const vector = vectors.slice(-1)[0] 28 | 29 | const input = Buffer.from(vector.input, 'hex') 30 | const key = Buffer.from(vector.key, 'hex') 31 | const salt = Buffer.from(vector.salt, 'hex') 32 | const personal = Buffer.from(vector.personal, 'hex') 33 | 34 | const expected = Buffer.from(vector.out, 'hex') 35 | const actual = Buffer.from(blake2b(vector.outlen, key, salt, personal).update(input).digest()) 36 | 37 | expect(actual).to.deep.equal(expected) 38 | }); 39 | 40 | it('streaming', function () { 41 | var instance = blake2b(32) 42 | var buf = Buffer.from('Hej, Verden') 43 | 44 | for (var i = 0; i < 10; i++) instance.update(buf) 45 | 46 | const out = Buffer.from(instance.digest()) 47 | 48 | expect(out.toString('hex')).to.equal('cbc20f347f5dfe37dc13231cbf7eaa4ec48e585ec055a96839b213f62bd8ce00'); 49 | }); 50 | 51 | it('streaming with key', function () { 52 | var key = Buffer.alloc(32) 53 | key.fill('lo') 54 | 55 | var instance = blake2b(32, key) 56 | var buf = Buffer.from('Hej, Verden') 57 | 58 | for (var i = 0; i < 10; i++) instance.update(buf) 59 | 60 | const out = Buffer.from(instance.digest()) 61 | 62 | expect(out.toString('hex')).to.equal('405f14acbeeb30396b8030f78e6a84bab0acf08cb1376aa200a500f669f675dc') 63 | }); 64 | 65 | it('streaming with hash length', function () { 66 | const instance = blake2b(16) 67 | const buf = Buffer.from('Hej, Verden') 68 | 69 | for (let i = 0; i < 10; i++) instance.update(buf) 70 | 71 | const out = Buffer.from(instance.digest()) 72 | 73 | expect(out.toString('hex')).to.equal('decacdcc3c61948c79d9f8dee5b6aa99') 74 | }); 75 | 76 | it('calling update() after digest() should error', function () { 77 | const instance = blake2b(16) 78 | const buf = Buffer.from('Hej, Verden') 79 | 80 | instance.update(buf) 81 | instance.digest() 82 | 83 | expect(() => instance.update(buf)).to.throw() 84 | }); 85 | 86 | it('streaming with key and hash length', function () { 87 | const key = Buffer.alloc(32) 88 | key.fill('lo') 89 | 90 | const instance = blake2b(16, key) 91 | const buf = Buffer.from('Hej, Verden') 92 | 93 | for (let i = 0; i < 10; i++) instance.update(buf) 94 | 95 | const out = Buffer.from(instance.digest()) 96 | 97 | expect(out.toString('hex')).to.equal('fb43f0ab6872cbfd39ec4f8a1bc6fb37') 98 | }); 99 | }); 100 | -------------------------------------------------------------------------------- /test/helpers/node-loader.ts: -------------------------------------------------------------------------------- 1 | import { readFileSync } from 'fs'; 2 | import setupWasm from '../../lib/setup.js'; 3 | 4 | const SIMD_FILENAME = './dist/simd.wasm'; 5 | const NON_SIMD_FILENAME = './dist/no-simd.wasm'; 6 | 7 | /** 8 | * Simple wasm loader for Node, that does not require bundlers: 9 | * it reads the wasm binaries from disk and instantiates them. 10 | * @returns argon2id function 11 | */ 12 | export default async function load() { 13 | return setupWasm( 14 | (importObject) => WebAssembly.instantiate(readFileSync(SIMD_FILENAME), importObject), 15 | (importObject) => WebAssembly.instantiate(readFileSync(NON_SIMD_FILENAME), importObject), 16 | ); 17 | } 18 | 19 | -------------------------------------------------------------------------------- /test/helpers/utils.js: -------------------------------------------------------------------------------- 1 | export function uint8ArrayToHex(bytes) { 2 | const res = new Array(); 3 | for (let c = 0; c < bytes.length; c++) { 4 | const hex = bytes[c].toString(16); 5 | res.push(hex.length < 2 ? '0' + hex : hex); 6 | } 7 | return res.join(''); 8 | } 9 | 10 | export function hexToUint8Array (string) { 11 | const buf = new Uint8Array(string.length / 2); 12 | // must be an even number of digits 13 | var strLen = string.length 14 | if (strLen % 2 !== 0) throw new TypeError('Invalid hex string') 15 | 16 | for (let i = 0; i < strLen / 2; ++i) { 17 | var parsed = parseInt(string.substr(i * 2, 2), 16) 18 | if (Number.isNaN(parsed)) throw new Error('Invalid byte') 19 | buf[i] = parsed 20 | } 21 | return buf 22 | } 23 | 24 | export const isNode = typeof globalThis.process === 'object' && typeof globalThis.process.versions === 'object'; 25 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | // used for tests only 2 | { 3 | "compilerOptions": { 4 | "target": "es6", 5 | "module": "esnext", 6 | "strict": true, 7 | "sourceMap": true, 8 | "allowJs": true, 9 | "noEmitOnError": true, 10 | "lib": ["dom", "esnext"] 11 | } 12 | } 13 | --------------------------------------------------------------------------------