├── .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 |
--------------------------------------------------------------------------------