Initial LisgloSIPS V2 implementation
This commit is contained in:
+20
@@ -0,0 +1,20 @@
|
||||
Copyright (c) 2022, Proton AG
|
||||
Copyright (c) 2017, Emil Bay github@tixz.dk (for original blake2b code from https://github.com/emilbayes/blake2b)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
# Argon2id
|
||||
|
||||
Fast, lightweight Argon2id implementation for both browser and Node:
|
||||
- optimized for bundle size (< 7KB minified and gizipped, with wasm inlined as base64)
|
||||
- SIMD support, with automatic fallback to non-SIMD binary if not supported (e.g. in Safari)
|
||||
- performance is comparable to or better than [argon2-browser](https://github.com/antelle/argon2-browser).
|
||||
|
||||
We initially tried implementing a solution in pure JS (no Wasm) but the running time was unacceptable.
|
||||
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.
|
||||
|
||||
## Install
|
||||
|
||||
Install from npm (compiled wasm files included):
|
||||
|
||||
```sh
|
||||
npm i argon2id
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
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:
|
||||
|
||||
```js
|
||||
import loadArgon2idWasm from 'argon2id';
|
||||
|
||||
const argon2id = await loadArgon2idWasm();
|
||||
const hash = argon2id({
|
||||
password: new Uint8Array(...),
|
||||
salt: crypto.getRandomValues(new Uint8Array(32)),
|
||||
parallelism: 4,
|
||||
passes: 3,
|
||||
memorySize: 2**16
|
||||
});
|
||||
```
|
||||
Refer to the [Argon2 RFC](https://www.rfc-editor.org/rfc/rfc9106.html#name-parameter-choice) for details about how to pick the parameters.
|
||||
|
||||
**Note about memory usage:** every call to `loadArgon2idWasm` will instantiate and run a separate Wasm instance, with separate memory.
|
||||
The used Wasm memory is cleared after each call to `argon2id`, but it isn't deallocated (this is due to Wasm limitations).
|
||||
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.
|
||||
|
||||
### Custom Wasm loaders
|
||||
|
||||
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.
|
||||
|
||||
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):
|
||||
|
||||
```js
|
||||
import fs from 'fs';
|
||||
import setupWasm from 'argon2id/lib/setup.js';
|
||||
|
||||
// point to compiled binaries
|
||||
const SIMD_FILENAME = 'argon2id/dist/simd.wasm';
|
||||
const NON_SIMD_FILENAME = 'argon2id/dist/no-simd.wasm';
|
||||
|
||||
const argon2id = await setupWasm(
|
||||
(importObject) => WebAssembly.instantiate(fs.readFileSync(SIMD_FILENAME), importObject),
|
||||
(importObject) => WebAssembly.instantiate(fs.readFileSync(NON_SIMD_FILENAME), importObject),
|
||||
);
|
||||
```
|
||||
|
||||
Using the same principle, for browsers you can use bundlers with simple base-64 file loaders.
|
||||
|
||||
## Compiling
|
||||
|
||||
**The npm package already includes the compiled binaries.**<br>
|
||||
If you fork the repo, you'll have to manually compile wasm (Docker required):
|
||||
```sh
|
||||
npm run build
|
||||
```
|
||||
|
||||
The resulting binaries will be under `dist/`.
|
||||
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`.
|
||||
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
# Vendored argon2id
|
||||
|
||||
- Package: `argon2id`
|
||||
- Version: `1.0.1`
|
||||
- Source: `https://www.npmjs.com/package/argon2id`
|
||||
- Repository: `https://github.com/openpgpjs/argon2id`
|
||||
- License: MIT, see `LICENSE`.
|
||||
|
||||
This package is vendored because the local npm registry path was unavailable during S09, while Server B runs Node.js 22 and does not expose built-in `crypto.argon2Sync`. The project code first uses Node's native Argon2id when present and falls back to this WASM implementation.
|
||||
|
||||
WASM SHA-256:
|
||||
|
||||
```text
|
||||
dist/no-simd.wasm 1F16D8DE5A6D8A3A4FAD5885C4784F57713D3626FD7D55F852FD79ECADBC4F8E
|
||||
dist/simd.wasm 50647E10FC8E0ADBA4F60726F3F0D77DB0E3C73516B05A83AD10390B3A8B4B0E
|
||||
```
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
import type { computeHash, Argon2idParams } from "./lib/setup";
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* The loaded module is then cached across `argon2id` calls.
|
||||
* NB: the used Wasm memory is cleared across runs, but not de-allocated.
|
||||
* Re-loading 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.
|
||||
* @returns argon2id function
|
||||
*/
|
||||
export function loadWasm(): Promise<computeHash>;
|
||||
export default loadWasm;
|
||||
export type { Argon2idParams, computeHash };
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
import setupWasm from './lib/setup.js';
|
||||
import wasmSIMD from './dist/simd.wasm';
|
||||
import wasmNonSIMD from './dist/no-simd.wasm';
|
||||
|
||||
const loadWasm = async () => setupWasm(
|
||||
(instanceObject) => wasmSIMD(instanceObject),
|
||||
(instanceObject) => wasmNonSIMD(instanceObject),
|
||||
);
|
||||
|
||||
export default loadWasm;
|
||||
+354
@@ -0,0 +1,354 @@
|
||||
import blake2b from "./blake2b.js"
|
||||
const TYPE = 2; // Argon2id
|
||||
const VERSION = 0x13;
|
||||
const TAGBYTES_MAX = 0xFFFFFFFF; // Math.pow(2, 32) - 1;
|
||||
const TAGBYTES_MIN = 4; // Math.pow(2, 32) - 1;
|
||||
const SALTBYTES_MAX = 0xFFFFFFFF; // Math.pow(2, 32) - 1;
|
||||
const SALTBYTES_MIN = 8;
|
||||
const passwordBYTES_MAX = 0xFFFFFFFF;// Math.pow(2, 32) - 1;
|
||||
const passwordBYTES_MIN = 8;
|
||||
const MEMBYTES_MAX = 0xFFFFFFFF;// Math.pow(2, 32) - 1;
|
||||
const ADBYTES_MAX = 0xFFFFFFFF; // Math.pow(2, 32) - 1; // associated data (optional)
|
||||
const SECRETBYTES_MAX = 32; // key (optional)
|
||||
|
||||
const ARGON2_BLOCK_SIZE = 1024;
|
||||
const ARGON2_PREHASH_DIGEST_LENGTH = 64;
|
||||
|
||||
const isLittleEndian = new Uint8Array(new Uint16Array([0xabcd]).buffer)[0] === 0xcd;
|
||||
|
||||
// store n as a little-endian 32-bit Uint8Array inside buf (at buf[i:i+3])
|
||||
function LE32(buf, n, i) {
|
||||
buf[i+0] = n;
|
||||
buf[i+1] = n >> 8;
|
||||
buf[i+2] = n >> 16;
|
||||
buf[i+3] = n >> 24;
|
||||
return buf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store n as a 64-bit LE number in the given buffer (from buf[i] to buf[i+7])
|
||||
* @param {Uint8Array} buf
|
||||
* @param {Number} n
|
||||
* @param {Number} i
|
||||
*/
|
||||
function LE64(buf, n, i) {
|
||||
if (n > Number.MAX_SAFE_INTEGER) throw new Error("LE64: large numbers unsupported");
|
||||
// ECMAScript standard has engines convert numbers to 32-bit integers for bitwise operations
|
||||
// shifting by 32 or more bits is not supported (https://stackoverflow.com/questions/6729122/javascript-bit-shift-number-wraps)
|
||||
// so we manually extract each byte
|
||||
let remainder = n;
|
||||
for (let offset = i; offset < i+7; offset++) { // last byte can be ignored as it would overflow MAX_SAFE_INTEGER
|
||||
buf[offset] = remainder; // implicit & 0xff
|
||||
remainder = (remainder - buf[offset]) / 256;
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Variable-Length Hash Function H'
|
||||
* @param {Number} outlen - T
|
||||
* @param {Uint8Array} X - value to hash
|
||||
* @param {Uint8Array} res - output buffer, of length `outlength` or larger
|
||||
*/
|
||||
function H_(outlen, X, res) {
|
||||
const V = new Uint8Array(64); // no need to keep around all V_i
|
||||
|
||||
const V1_in = new Uint8Array(4 + X.length);
|
||||
LE32(V1_in, outlen, 0);
|
||||
V1_in.set(X, 4);
|
||||
if (outlen <= 64) {
|
||||
// H'^T(A) = H^T(LE32(T)||A)
|
||||
blake2b(outlen).update(V1_in).digest(res);
|
||||
return res
|
||||
}
|
||||
|
||||
const r = Math.ceil(outlen / 32) - 2;
|
||||
|
||||
// Let V_i be a 64-byte block and W_i be its first 32 bytes.
|
||||
// V_1 = H^(64)(LE32(T)||A)
|
||||
// V_2 = H^(64)(V_1)
|
||||
// ...
|
||||
// V_r = H^(64)(V_{r-1})
|
||||
// V_{r+1} = H^(T-32*r)(V_{r})
|
||||
// H'^T(X) = W_1 || W_2 || ... || W_r || V_{r+1}
|
||||
for (let i = 0; i < r; i++) {
|
||||
blake2b(64).update(i === 0 ? V1_in : V).digest(V);
|
||||
// store W_i in result buffer already
|
||||
res.set(V.subarray(0, 32), i*32)
|
||||
}
|
||||
// V_{r+1}
|
||||
const V_r1 = new Uint8Array(blake2b(outlen - 32*r).update(V).digest());
|
||||
res.set(V_r1, r*32);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
// compute buf = xs ^ ys
|
||||
function XOR(wasmContext, buf, xs, ys) {
|
||||
wasmContext.fn.XOR(
|
||||
buf.byteOffset,
|
||||
xs.byteOffset,
|
||||
ys.byteOffset,
|
||||
);
|
||||
return buf
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Uint8Array} X (read-only)
|
||||
* @param {Uint8Array} Y (read-only)
|
||||
* @param {Uint8Array} R - output buffer
|
||||
* @returns
|
||||
*/
|
||||
function G(wasmContext, X, Y, R) {
|
||||
wasmContext.fn.G(
|
||||
X.byteOffset,
|
||||
Y.byteOffset,
|
||||
R.byteOffset,
|
||||
wasmContext.refs.gZ.byteOffset
|
||||
);
|
||||
return R;
|
||||
}
|
||||
|
||||
function G2(wasmContext, X, Y, R) {
|
||||
wasmContext.fn.G2(
|
||||
X.byteOffset,
|
||||
Y.byteOffset,
|
||||
R.byteOffset,
|
||||
wasmContext.refs.gZ.byteOffset
|
||||
);
|
||||
return R;
|
||||
}
|
||||
|
||||
// Generator for data-independent J1, J2. Each `next()` invocation returns a new pair of values.
|
||||
function* makePRNG(wasmContext, pass, lane, slice, m_, totalPasses, segmentLength, segmentOffset) {
|
||||
// For each segment, we do the following. First, we compute the value Z as:
|
||||
// Z= ( LE64(r) || LE64(l) || LE64(sl) || LE64(m') || LE64(t) || LE64(y) )
|
||||
wasmContext.refs.prngTmp.fill(0);
|
||||
const Z = wasmContext.refs.prngTmp.subarray(0, 6 * 8);
|
||||
LE64(Z, pass, 0);
|
||||
LE64(Z, lane, 8);
|
||||
LE64(Z, slice, 16);
|
||||
LE64(Z, m_, 24);
|
||||
LE64(Z, totalPasses, 32);
|
||||
LE64(Z, TYPE, 40);
|
||||
|
||||
// Then we compute q/(128*SL) 1024-byte values
|
||||
// G( ZERO(1024),
|
||||
// G( ZERO(1024), Z || LE64(1) || ZERO(968) ) ),
|
||||
// ...,
|
||||
// G( ZERO(1024),
|
||||
// G( ZERO(1024), Z || LE64(q/(128*SL)) || ZERO(968) )),
|
||||
for(let i = 1; i <= segmentLength; i++) {
|
||||
// tmp.set(Z); // no need to re-copy
|
||||
LE64(wasmContext.refs.prngTmp, i, Z.length); // tmp.set(ZER0968) not necessary, memory already zeroed
|
||||
const g2 = G2(wasmContext, wasmContext.refs.ZERO1024, wasmContext.refs.prngTmp, wasmContext.refs.prngR );
|
||||
|
||||
// each invocation of G^2 outputs 1024 bytes that are to be partitioned into 8-bytes values, take as X1 || X2
|
||||
// NB: the first generated pair must be used for the first block of the segment, and so on.
|
||||
// Hence, if some blocks are skipped (e.g. during the first pass), the corresponding J1J2 are discarded based on the given segmentOffset.
|
||||
for(let k = i === 1 ? segmentOffset*8 : 0; k < g2.length; k += 8) {
|
||||
yield g2.subarray(k, k+8);
|
||||
}
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
function validateParams({ type, version, tagLength, password, salt, ad, secret, parallelism, memorySize, passes }) {
|
||||
const assertLength = (name, value, min, max) => {
|
||||
if (value < min || value > max) { throw new Error(`${name} size should be between ${min} and ${max} bytes`); }
|
||||
}
|
||||
|
||||
if (type !== TYPE || version !== VERSION) throw new Error('Unsupported type or version');
|
||||
assertLength('password', password, passwordBYTES_MIN, passwordBYTES_MAX);
|
||||
assertLength('salt', salt, SALTBYTES_MIN, SALTBYTES_MAX);
|
||||
assertLength('tag', tagLength, TAGBYTES_MIN, TAGBYTES_MAX);
|
||||
assertLength('memory', memorySize, 8*parallelism, MEMBYTES_MAX);
|
||||
// optional fields
|
||||
ad && assertLength('associated data', ad, 0, ADBYTES_MAX);
|
||||
secret && assertLength('secret', secret, 0, SECRETBYTES_MAX);
|
||||
|
||||
return { type, version, tagLength, password, salt, ad, secret, lanes: parallelism, memorySize, passes };
|
||||
}
|
||||
|
||||
const KB = 1024;
|
||||
const WASM_PAGE_SIZE = 64 * KB;
|
||||
|
||||
export default function argon2id(params, { memory, instance: wasmInstance }) {
|
||||
if (!isLittleEndian) throw new Error('BigEndian system not supported'); // optmisations assume LE system
|
||||
|
||||
const ctx = validateParams({ type: TYPE, version: VERSION, ...params });
|
||||
|
||||
const { G:wasmG, G2:wasmG2, xor:wasmXOR, getLZ:wasmLZ } = wasmInstance.exports;
|
||||
const wasmRefs = {};
|
||||
const wasmFn = {};
|
||||
wasmFn.G = wasmG;
|
||||
wasmFn.G2 = wasmG2;
|
||||
wasmFn.XOR = wasmXOR;
|
||||
|
||||
// The actual number of blocks is m', which is m rounded down to the nearest multiple of 4*p.
|
||||
const m_ = 4 * ctx.lanes * Math.floor(ctx.memorySize / (4 * ctx.lanes));
|
||||
const requiredMemory = m_ * ARGON2_BLOCK_SIZE + 10 * KB; // Additional KBs for utility references
|
||||
if (memory.buffer.byteLength < requiredMemory) {
|
||||
const missing = Math.ceil((requiredMemory - memory.buffer.byteLength) / WASM_PAGE_SIZE)
|
||||
// If enough memory is available, the `memory.buffer` is internally detached and the reference updated.
|
||||
// Otherwise, the operation fails, and the original memory can still be used.
|
||||
memory.grow(missing)
|
||||
}
|
||||
|
||||
let offset = 0;
|
||||
// Init wasm memory needed in other functions
|
||||
wasmRefs.gZ = new Uint8Array(memory.buffer, offset, ARGON2_BLOCK_SIZE); offset+= wasmRefs.gZ.length;
|
||||
wasmRefs.prngR = new Uint8Array(memory.buffer, offset, ARGON2_BLOCK_SIZE); offset+=wasmRefs.prngR.length;
|
||||
wasmRefs.prngTmp = new Uint8Array(memory.buffer, offset, ARGON2_BLOCK_SIZE); offset+=wasmRefs.prngTmp.length;
|
||||
wasmRefs.ZERO1024 = new Uint8Array(memory.buffer, offset, 1024); offset+=wasmRefs.ZERO1024.length;
|
||||
// Init wasm memory needed locally
|
||||
const lz = new Uint32Array(memory.buffer, offset, 2); offset+=lz.length * Uint32Array.BYTES_PER_ELEMENT;
|
||||
const wasmContext = { fn: wasmFn, refs: wasmRefs };
|
||||
const newBlock = new Uint8Array(memory.buffer, offset, ARGON2_BLOCK_SIZE); offset+=newBlock.length;
|
||||
const blockMemory = new Uint8Array(memory.buffer, offset, ctx.memorySize * ARGON2_BLOCK_SIZE);
|
||||
const allocatedMemory = new Uint8Array(memory.buffer, 0, offset);
|
||||
|
||||
// 1. Establish H_0
|
||||
const H0 = getH0(ctx);
|
||||
|
||||
// 2. Allocate the memory as m' 1024-byte blocks
|
||||
// For p lanes, the memory is organized in a matrix B[i][j] of blocks with p rows (lanes) and q = m' / p columns.
|
||||
const q = m_ / ctx.lanes;
|
||||
const B = new Array(ctx.lanes).fill(null).map(() => new Array(q));
|
||||
const initBlock = (i, j) => {
|
||||
B[i][j] = blockMemory.subarray(i*q*1024 + j*1024, (i*q*1024 + j*1024) + ARGON2_BLOCK_SIZE);
|
||||
return B[i][j];
|
||||
}
|
||||
|
||||
for (let i = 0; i < ctx.lanes; i++) {
|
||||
// const LEi = LE0; // since p = 1 for us
|
||||
const tmp = new Uint8Array(H0.length + 8);
|
||||
// 3. Compute B[i][0] for all i ranging from (and including) 0 to (not including) p
|
||||
// B[i][0] = H'^(1024)(H_0 || LE32(0) || LE32(i))
|
||||
tmp.set(H0); LE32(tmp, 0, H0.length); LE32(tmp, i, H0.length + 4);
|
||||
H_(ARGON2_BLOCK_SIZE, tmp, initBlock(i, 0));
|
||||
// 4. Compute B[i][1] for all i ranging from (and including) 0 to (not including) p
|
||||
// B[i][1] = H'^(1024)(H_0 || LE32(1) || LE32(i))
|
||||
LE32(tmp, 1, H0.length);
|
||||
H_(ARGON2_BLOCK_SIZE, tmp, initBlock(i, 1));
|
||||
}
|
||||
|
||||
// 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
|
||||
// to (not including) q. The computation MUST proceed slicewise (Section 3.4) : first, blocks from slice 0 are computed for all lanes
|
||||
// (in an arbitrary order of lanes), then blocks from slice 1 are computed, etc.
|
||||
const SL = 4; // vertical slices
|
||||
const segmentLength = q / SL;
|
||||
for (let pass = 0; pass < ctx.passes; pass++) {
|
||||
// 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
|
||||
for (let sl = 0; sl < SL; sl++) {
|
||||
const isDataIndependent = pass === 0 && sl <= 1;
|
||||
for (let i = 0; i < ctx.lanes; i++) { // lane
|
||||
// On the first slice of the first pass, blocks 0 and 1 are already filled
|
||||
let segmentOffset = sl === 0 && pass === 0 ? 2 : 0;
|
||||
// no need to generate all J1J2s, use iterator/generator that creates the value on the fly (to save memory)
|
||||
const PRNG = isDataIndependent ? makePRNG(wasmContext, pass, i, sl, m_, ctx.passes, segmentLength, segmentOffset) : null;
|
||||
for (segmentOffset; segmentOffset < segmentLength; segmentOffset++) {
|
||||
const j = sl * segmentLength + segmentOffset;
|
||||
const prevBlock = j > 0 ? B[i][j-1] : B[i][q-1]; // B[i][(j-1) mod q]
|
||||
|
||||
// we can assume the PRNG is never done
|
||||
const J1J2 = isDataIndependent ? PRNG.next().value : prevBlock; // .subarray(0, 8) not required since we only pass the byteOffset to wasm
|
||||
// The block indices l and z are determined for each i, j differently for Argon2d, Argon2i, and Argon2id.
|
||||
wasmLZ(lz.byteOffset, J1J2.byteOffset, i, ctx.lanes, pass, sl, segmentOffset, SL, segmentLength)
|
||||
const l = lz[0]; const z = lz[1];
|
||||
// for (let i = 0; i < p; i++ )
|
||||
// B[i][j] = G(B[i][j-1], B[l][z])
|
||||
// The block indices l and z are determined for each i, j differently for Argon2d, Argon2i, and Argon2id.
|
||||
if (pass === 0) initBlock(i, j);
|
||||
G(wasmContext, prevBlock, B[l][z], pass > 0 ? newBlock : B[i][j]);
|
||||
|
||||
// 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
|
||||
if (pass > 0) XOR(wasmContext, B[i][j], newBlock, B[i][j])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 7. After t steps have been iterated, the final block C is computed as the XOR of the last column:
|
||||
// C = B[0][q-1] XOR B[1][q-1] XOR ... XOR B[p-1][q-1]
|
||||
const C = B[0][q-1];
|
||||
for(let i = 1; i < ctx.lanes; i++) {
|
||||
XOR(wasmContext, C, C, B[i][q-1])
|
||||
}
|
||||
|
||||
const tag = H_(ctx.tagLength, C, new Uint8Array(ctx.tagLength));
|
||||
// clear memory since the module might be cached
|
||||
allocatedMemory.fill(0) // clear sensitive contents
|
||||
memory.grow(0) // allow deallocation
|
||||
// 8. The output tag is computed as H'^T(C).
|
||||
return tag;
|
||||
|
||||
}
|
||||
|
||||
function getH0(ctx) {
|
||||
const H = blake2b(ARGON2_PREHASH_DIGEST_LENGTH);
|
||||
const ZERO32 = new Uint8Array(4);
|
||||
const params = new Uint8Array(24);
|
||||
LE32(params, ctx.lanes, 0);
|
||||
LE32(params, ctx.tagLength, 4);
|
||||
LE32(params, ctx.memorySize, 8);
|
||||
LE32(params, ctx.passes, 12);
|
||||
LE32(params, ctx.version, 16);
|
||||
LE32(params, ctx.type, 20);
|
||||
|
||||
const toHash = [params];
|
||||
if (ctx.password) {
|
||||
toHash.push(LE32(new Uint8Array(4), ctx.password.length, 0))
|
||||
toHash.push(ctx.password)
|
||||
} else {
|
||||
toHash.push(ZERO32) // context.password.length
|
||||
}
|
||||
|
||||
if (ctx.salt) {
|
||||
toHash.push(LE32(new Uint8Array(4), ctx.salt.length, 0))
|
||||
toHash.push(ctx.salt)
|
||||
} else {
|
||||
toHash.push(ZERO32) // context.salt.length
|
||||
}
|
||||
|
||||
if (ctx.secret) {
|
||||
toHash.push(LE32(new Uint8Array(4), ctx.secret.length, 0))
|
||||
toHash.push(ctx.secret)
|
||||
// todo clear secret?
|
||||
} else {
|
||||
toHash.push(ZERO32) // context.secret.length
|
||||
}
|
||||
|
||||
if (ctx.ad) {
|
||||
toHash.push(LE32(new Uint8Array(4), ctx.ad.length, 0))
|
||||
toHash.push(ctx.ad)
|
||||
} else {
|
||||
toHash.push(ZERO32) // context.ad.length
|
||||
}
|
||||
H.update(concatArrays(toHash))
|
||||
|
||||
const outputBuffer = H.digest();
|
||||
return new Uint8Array(outputBuffer);
|
||||
}
|
||||
|
||||
function concatArrays(arrays) {
|
||||
if (arrays.length === 1) return arrays[0];
|
||||
|
||||
let totalLength = 0;
|
||||
for (let i = 0; i < arrays.length; i++) {
|
||||
if (!(arrays[i] instanceof Uint8Array)) {
|
||||
throw new Error('concatArrays: Data must be in the form of a Uint8Array');
|
||||
}
|
||||
|
||||
totalLength += arrays[i].length;
|
||||
}
|
||||
|
||||
const result = new Uint8Array(totalLength);
|
||||
let pos = 0;
|
||||
arrays.forEach((element) => {
|
||||
result.set(element, pos);
|
||||
pos += element.length;
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
+256
@@ -0,0 +1,256 @@
|
||||
// Adapted from the reference implementation in RFC7693
|
||||
// Initial port to Javascript by https://github.com/dcposch and https://github.com/emilbayes
|
||||
|
||||
// Uint64 values are represented using two Uint32s, stored as little endian
|
||||
// NB: Uint32Arrays endianness depends on the underlying system, so for interoperability, conversions between Uint8Array and Uint32Arrays
|
||||
// need to be manually handled
|
||||
|
||||
// 64-bit unsigned addition (little endian, in place)
|
||||
// Sets a[i,i+1] += b[j,j+1]
|
||||
// `a` and `b` must be Uint32Array(2)
|
||||
function ADD64 (a, i, b, j) {
|
||||
a[i] += b[j];
|
||||
a[i+1] += b[j+1] + (a[i] < b[j]); // add carry
|
||||
}
|
||||
|
||||
// Increment 64-bit little-endian unsigned value by `c` (in place)
|
||||
// `a` must be Uint32Array(2)
|
||||
function INC64 (a, c) {
|
||||
a[0] += c;
|
||||
a[1] += (a[0] < c);
|
||||
}
|
||||
|
||||
// G Mixing function
|
||||
// The ROTRs are inlined for speed
|
||||
function G (v, m, a, b, c, d, ix, iy) {
|
||||
ADD64(v, a, v, b) // v[a,a+1] += v[b,b+1]
|
||||
ADD64(v, a, m, ix) // v[a, a+1] += x ... x0
|
||||
|
||||
// v[d,d+1] = (v[d,d+1] xor v[a,a+1]) rotated to the right by 32 bits
|
||||
let xor0 = v[d] ^ v[a]
|
||||
let xor1 = v[d + 1] ^ v[a + 1]
|
||||
v[d] = xor1
|
||||
v[d + 1] = xor0
|
||||
|
||||
ADD64(v, c, v, d)
|
||||
|
||||
// v[b,b+1] = (v[b,b+1] xor v[c,c+1]) rotated right by 24 bits
|
||||
xor0 = v[b] ^ v[c]
|
||||
xor1 = v[b + 1] ^ v[c + 1]
|
||||
v[b] = (xor0 >>> 24) ^ (xor1 << 8)
|
||||
v[b + 1] = (xor1 >>> 24) ^ (xor0 << 8)
|
||||
|
||||
ADD64(v, a, v, b)
|
||||
ADD64(v, a, m, iy)
|
||||
|
||||
// v[d,d+1] = (v[d,d+1] xor v[a,a+1]) rotated right by 16 bits
|
||||
xor0 = v[d] ^ v[a]
|
||||
xor1 = v[d + 1] ^ v[a + 1]
|
||||
v[d] = (xor0 >>> 16) ^ (xor1 << 16)
|
||||
v[d + 1] = (xor1 >>> 16) ^ (xor0 << 16)
|
||||
|
||||
ADD64(v, c, v, d)
|
||||
|
||||
// v[b,b+1] = (v[b,b+1] xor v[c,c+1]) rotated right by 63 bits
|
||||
xor0 = v[b] ^ v[c]
|
||||
xor1 = v[b + 1] ^ v[c + 1]
|
||||
v[b] = (xor1 >>> 31) ^ (xor0 << 1)
|
||||
v[b + 1] = (xor0 >>> 31) ^ (xor1 << 1)
|
||||
}
|
||||
|
||||
// Initialization Vector
|
||||
const BLAKE2B_IV32 = new Uint32Array([
|
||||
0xF3BCC908, 0x6A09E667, 0x84CAA73B, 0xBB67AE85,
|
||||
0xFE94F82B, 0x3C6EF372, 0x5F1D36F1, 0xA54FF53A,
|
||||
0xADE682D1, 0x510E527F, 0x2B3E6C1F, 0x9B05688C,
|
||||
0xFB41BD6B, 0x1F83D9AB, 0x137E2179, 0x5BE0CD19
|
||||
])
|
||||
|
||||
// These are offsets into a Uint64 buffer.
|
||||
// Multiply them all by 2 to make them offsets into a Uint32 buffer
|
||||
const SIGMA = new Uint8Array([
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
|
||||
14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3,
|
||||
11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4,
|
||||
7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8,
|
||||
9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13,
|
||||
2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9,
|
||||
12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11,
|
||||
13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10,
|
||||
6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5,
|
||||
10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0,
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
|
||||
14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3
|
||||
].map(x => x * 2))
|
||||
|
||||
// Compression function. 'last' flag indicates last block.
|
||||
// Note: we're representing 16 uint64s as 32 uint32s
|
||||
function compress(S, last) {
|
||||
const v = new Uint32Array(32)
|
||||
const m = new Uint32Array(S.b.buffer, S.b.byteOffset, 32)
|
||||
|
||||
// init work variables
|
||||
for (let i = 0; i < 16; i++) {
|
||||
v[i] = S.h[i]
|
||||
v[i + 16] = BLAKE2B_IV32[i]
|
||||
}
|
||||
|
||||
// low 64 bits of offset
|
||||
v[24] ^= S.t0[0]
|
||||
v[25] ^= S.t0[1]
|
||||
// high 64 bits not supported (`t1`), offset may not be higher than 2**53-1
|
||||
|
||||
// if last block
|
||||
const f0 = last ? 0xFFFFFFFF : 0;
|
||||
v[28] ^= f0;
|
||||
v[29] ^= f0;
|
||||
|
||||
// twelve rounds of mixing
|
||||
for (let i = 0; i < 12; i++) {
|
||||
// ROUND(r)
|
||||
const i16 = i << 4;
|
||||
G(v, m, 0, 8, 16, 24, SIGMA[i16 + 0], SIGMA[i16 + 1])
|
||||
G(v, m, 2, 10, 18, 26, SIGMA[i16 + 2], SIGMA[i16 + 3])
|
||||
G(v, m, 4, 12, 20, 28, SIGMA[i16 + 4], SIGMA[i16 + 5])
|
||||
G(v, m, 6, 14, 22, 30, SIGMA[i16 + 6], SIGMA[i16 + 7])
|
||||
G(v, m, 0, 10, 20, 30, SIGMA[i16 + 8], SIGMA[i16 + 9])
|
||||
G(v, m, 2, 12, 22, 24, SIGMA[i16 + 10], SIGMA[i16 + 11])
|
||||
G(v, m, 4, 14, 16, 26, SIGMA[i16 + 12], SIGMA[i16 + 13])
|
||||
G(v, m, 6, 8, 18, 28, SIGMA[i16 + 14], SIGMA[i16 + 15])
|
||||
}
|
||||
|
||||
for (let i = 0; i < 16; i++) {
|
||||
S.h[i] ^= v[i] ^ v[i + 16]
|
||||
}
|
||||
}
|
||||
|
||||
// Creates a BLAKE2b hashing context
|
||||
// Requires an output length between 1 and 64 bytes
|
||||
// Takes an optional Uint8Array key
|
||||
class Blake2b {
|
||||
constructor(outlen, key, salt, personal) {
|
||||
const params = new Uint8Array(64)
|
||||
// 0: outlen, keylen, fanout, depth
|
||||
// 4: leaf length, sequential mode
|
||||
// 8: node offset
|
||||
// 12: node offset
|
||||
// 16: node depth, inner length, rfu
|
||||
// 20: rfu
|
||||
// 24: rfu
|
||||
// 28: rfu
|
||||
// 32: salt
|
||||
// 36: salt
|
||||
// 40: salt
|
||||
// 44: salt
|
||||
// 48: personal
|
||||
// 52: personal
|
||||
// 56: personal
|
||||
// 60: personal
|
||||
|
||||
// init internal state
|
||||
this.S = {
|
||||
b: new Uint8Array(BLOCKBYTES),
|
||||
h: new Uint32Array(OUTBYTES_MAX / 4),
|
||||
t0: new Uint32Array(2), // input counter `t`, lower 64-bits only
|
||||
c: 0, // `fill`, pointer within buffer, up to `BLOCKBYTES`
|
||||
outlen // output length in bytes
|
||||
}
|
||||
|
||||
// init parameter block
|
||||
params[0] = outlen
|
||||
if (key) params[1] = key.length
|
||||
params[2] = 1 // fanout
|
||||
params[3] = 1 // depth
|
||||
if (salt) params.set(salt, 32)
|
||||
if (personal) params.set(personal, 48)
|
||||
const params32 = new Uint32Array(params.buffer, params.byteOffset, params.length / Uint32Array.BYTES_PER_ELEMENT);
|
||||
|
||||
// initialize hash state
|
||||
for (let i = 0; i < 16; i++) {
|
||||
this.S.h[i] = BLAKE2B_IV32[i] ^ params32[i];
|
||||
}
|
||||
|
||||
// key the hash, if applicable
|
||||
if (key) {
|
||||
const block = new Uint8Array(BLOCKBYTES)
|
||||
block.set(key)
|
||||
this.update(block)
|
||||
}
|
||||
}
|
||||
|
||||
// Updates a BLAKE2b streaming hash
|
||||
// Requires Uint8Array (byte array)
|
||||
update(input) {
|
||||
if (!(input instanceof Uint8Array)) throw new Error('Input must be Uint8Array or Buffer')
|
||||
// for (let i = 0; i < input.length; i++) {
|
||||
// if (this.S.c === BLOCKBYTES) { // buffer full
|
||||
// INC64(this.S.t0, this.S.c) // add counters
|
||||
// compress(this.S, false)
|
||||
// this.S.c = 0 // empty buffer
|
||||
// }
|
||||
// this.S.b[this.S.c++] = input[i]
|
||||
// }
|
||||
let i = 0
|
||||
while(i < input.length) {
|
||||
if (this.S.c === BLOCKBYTES) { // buffer full
|
||||
INC64(this.S.t0, this.S.c) // add counters
|
||||
compress(this.S, false)
|
||||
this.S.c = 0 // empty buffer
|
||||
}
|
||||
let left = BLOCKBYTES - this.S.c
|
||||
this.S.b.set(input.subarray(i, i + left), this.S.c) // end index can be out of bounds
|
||||
const fill = Math.min(left, input.length - i)
|
||||
this.S.c += fill
|
||||
i += fill
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a BLAKE2b hash, either filling the given Uint8Array or allocating a new one
|
||||
* @param {Uint8Array} [prealloc] - optional preallocated buffer
|
||||
* @returns {ArrayBuffer} message digest
|
||||
*/
|
||||
digest(prealloc) {
|
||||
INC64(this.S.t0, this.S.c) // mark last block offset
|
||||
|
||||
// final block, padded
|
||||
this.S.b.fill(0, this.S.c);
|
||||
this.S.c = BLOCKBYTES;
|
||||
compress(this.S, true)
|
||||
|
||||
const out = prealloc || new Uint8Array(this.S.outlen);
|
||||
for (let i = 0; i < this.S.outlen; i++) {
|
||||
// must be loaded individually since default Uint32 endianness is platform dependant
|
||||
out[i] = this.S.h[i >> 2] >> (8 * (i & 3))
|
||||
}
|
||||
this.S.h = null; // prevent calling `update` after `digest`
|
||||
return out.buffer;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default function createHash(outlen, key, salt, personal) {
|
||||
if (outlen > OUTBYTES_MAX) throw new Error(`outlen must be at most ${OUTBYTES_MAX} (given: ${outlen})`)
|
||||
if (key) {
|
||||
if (!(key instanceof Uint8Array)) throw new Error('key must be Uint8Array or Buffer')
|
||||
if (key.length > KEYBYTES_MAX) throw new Error(`key size must be at most ${KEYBYTES_MAX} (given: ${key.length})`)
|
||||
}
|
||||
if (salt) {
|
||||
if (!(salt instanceof Uint8Array)) throw new Error('salt must be Uint8Array or Buffer')
|
||||
if (salt.length !== SALTBYTES) throw new Error(`salt must be exactly ${SALTBYTES} (given: ${salt.length}`)
|
||||
}
|
||||
if (personal) {
|
||||
if (!(personal instanceof Uint8Array)) throw new Error('personal must be Uint8Array or Buffer')
|
||||
if (personal.length !== PERSONALBYTES) throw new Error(`salt must be exactly ${PERSONALBYTES} (given: ${personal.length}`)
|
||||
}
|
||||
|
||||
return new Blake2b(outlen, key, salt, personal)
|
||||
}
|
||||
|
||||
const OUTBYTES_MAX = 64;
|
||||
const KEYBYTES_MAX = 64;
|
||||
const SALTBYTES = 16;
|
||||
const PERSONALBYTES = 16;
|
||||
const BLOCKBYTES = 128;
|
||||
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
export interface Argon2idParams {
|
||||
password: Uint8Array;
|
||||
salt: Uint8Array;
|
||||
/** Degree of parallelism (number of lanes) */
|
||||
parallelism: number;
|
||||
/** Number of iterations */
|
||||
passes: number;
|
||||
/** Memory cost in kibibytes */
|
||||
memorySize: number;
|
||||
/** Output tag length */
|
||||
tagLength: number;
|
||||
/** Associated Data */
|
||||
ad?: Uint8Array;
|
||||
/** Secret Data */
|
||||
secret?: Uint8Array;
|
||||
}
|
||||
|
||||
declare function argon2id(params: Argon2idParams): Uint8Array;
|
||||
export type computeHash = typeof argon2id;
|
||||
|
||||
type MaybePromise<T> = T | Promise<T>;
|
||||
|
||||
declare function customInstanceLoader(importObject: WebAssembly.Imports): MaybePromise<WebAssembly.WebAssemblyInstantiatedSource>;
|
||||
|
||||
/**
|
||||
* Load Wasm module and return argon2id wrapper.
|
||||
* It is platform-independent and it relies on the two functions given in input to instatiate the Wasm instances.
|
||||
* @param getSIMD - function instantiating and returning the SIMD Wasm instance
|
||||
* @param getNonSIMD - function instantiating and returning the non-SIMD Wasm instance
|
||||
* @returns {computeHash}
|
||||
*/
|
||||
export default function setupWasm(
|
||||
getSIMD: typeof customInstanceLoader,
|
||||
getNonSIMD: typeof customInstanceLoader,
|
||||
): Promise<computeHash>;
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
import argon2id from "./argon2id.js";
|
||||
|
||||
let isSIMDSupported;
|
||||
async function wasmLoader(memory, getSIMD, getNonSIMD) {
|
||||
const importObject = { env: { memory } };
|
||||
if (isSIMDSupported === undefined) {
|
||||
try {
|
||||
const loaded = await getSIMD(importObject);
|
||||
isSIMDSupported = true;
|
||||
return loaded;
|
||||
} catch(e) {
|
||||
isSIMDSupported = false;
|
||||
}
|
||||
}
|
||||
|
||||
const loader = isSIMDSupported ? getSIMD : getNonSIMD;
|
||||
return loader(importObject);
|
||||
}
|
||||
|
||||
export default async function setupWasm(getSIMD, getNonSIMD) {
|
||||
const memory = new WebAssembly.Memory({
|
||||
// in pages of 64KiB each
|
||||
// these values need to be compatible with those declared when building in `build-wasm`
|
||||
initial: 1040, // 65MB
|
||||
maximum: 65536, // 4GB
|
||||
});
|
||||
const wasmModule = await wasmLoader(memory, getSIMD, getNonSIMD);
|
||||
|
||||
/**
|
||||
* Argon2id hash function
|
||||
* @callback computeHash
|
||||
* @param {Object} params
|
||||
* @param {Uint8Array} params.password - password
|
||||
* @param {Uint8Array} params.salt - salt
|
||||
* @param {Integer} params.parallelism
|
||||
* @param {Integer} params.passes
|
||||
* @param {Integer} params.memorySize - in kibibytes
|
||||
* @param {Integer} params.tagLength - output tag length
|
||||
* @param {Uint8Array} [params.ad] - associated data (optional)
|
||||
* @param {Uint8Array} [params.secret] - secret data (optional)
|
||||
* @return {Uint8Array} argon2id hash
|
||||
*/
|
||||
const computeHash = (params) => argon2id(params, { instance: wasmModule.instance, memory });
|
||||
|
||||
return computeHash;
|
||||
}
|
||||
+308
@@ -0,0 +1,308 @@
|
||||
/**
|
||||
* Vectorised code mostly taken from: Argon2 reference C implementations (www.github.com/P-H-C/phc-winner-argon2)
|
||||
* Copyright 2015 Daniel Dinu, Dmitry Khovratovich, Jean-Philippe Aumasson, and Samuel Neves
|
||||
* Licence: CC0 1.0 Universal (https://creativecommons.org/publicdomain/zero/1.0)
|
||||
*/
|
||||
#include <stdint.h>
|
||||
// #include <stdio.h>
|
||||
#undef EMSCRIPTEN_KEEPALIVE
|
||||
#define EMSCRIPTEN_KEEPALIVE __attribute__((used)) __attribute__((retain))
|
||||
|
||||
#if defined(__SSSE3__) || defined(__SSE2__)
|
||||
#include <emmintrin.h>
|
||||
|
||||
#if defined(__SSSE3__)
|
||||
#include <tmmintrin.h>
|
||||
#define r16 \
|
||||
(_mm_setr_epi8(2, 3, 4, 5, 6, 7, 0, 1, 10, 11, 12, 13, 14, 15, 8, 9))
|
||||
#define r24 \
|
||||
(_mm_setr_epi8(3, 4, 5, 6, 7, 0, 1, 2, 11, 12, 13, 14, 15, 8, 9, 10))
|
||||
#define _mm_roti_epi64(x, c) \
|
||||
(-(c) == 32) \
|
||||
? _mm_shuffle_epi32((x), _MM_SHUFFLE(2, 3, 0, 1)) \
|
||||
: (-(c) == 24) \
|
||||
? _mm_shuffle_epi8((x), r24) \
|
||||
: (-(c) == 16) \
|
||||
? _mm_shuffle_epi8((x), r16) \
|
||||
: (-(c) == 63) \
|
||||
? _mm_xor_si128(_mm_srli_epi64((x), -(c)), \
|
||||
_mm_add_epi64((x), (x))) \
|
||||
: _mm_xor_si128(_mm_srli_epi64((x), -(c)), \
|
||||
_mm_slli_epi64((x), 64 - (-(c))))
|
||||
#else /* SSE2 */
|
||||
#define _mm_roti_epi64(r, c) \
|
||||
_mm_xor_si128(_mm_srli_epi64((r), -(c)), _mm_slli_epi64((r), 64 - (-(c))))
|
||||
#endif
|
||||
static __m128i fBlaMka(__m128i x, __m128i y) {
|
||||
const __m128i z = _mm_mul_epu32(x, y);
|
||||
return _mm_add_epi64(_mm_add_epi64(x, y), _mm_add_epi64(z, z));
|
||||
}
|
||||
|
||||
#define GB1(A0, B0, C0, D0, A1, B1, C1, D1) \
|
||||
do { \
|
||||
A0 = fBlaMka(A0, B0); \
|
||||
A1 = fBlaMka(A1, B1); \
|
||||
\
|
||||
D0 = _mm_xor_si128(D0, A0); \
|
||||
D1 = _mm_xor_si128(D1, A1); \
|
||||
\
|
||||
D0 = _mm_roti_epi64(D0, -32); \
|
||||
D1 = _mm_roti_epi64(D1, -32); \
|
||||
\
|
||||
C0 = fBlaMka(C0, D0); \
|
||||
C1 = fBlaMka(C1, D1); \
|
||||
\
|
||||
B0 = _mm_xor_si128(B0, C0); \
|
||||
B1 = _mm_xor_si128(B1, C1); \
|
||||
\
|
||||
B0 = _mm_roti_epi64(B0, -24); \
|
||||
B1 = _mm_roti_epi64(B1, -24); \
|
||||
} while ((void)0, 0)
|
||||
|
||||
#define GB2(A0, B0, C0, D0, A1, B1, C1, D1) \
|
||||
do { \
|
||||
A0 = fBlaMka(A0, B0); \
|
||||
A1 = fBlaMka(A1, B1); \
|
||||
\
|
||||
D0 = _mm_xor_si128(D0, A0); \
|
||||
D1 = _mm_xor_si128(D1, A1); \
|
||||
\
|
||||
D0 = _mm_roti_epi64(D0, -16); \
|
||||
D1 = _mm_roti_epi64(D1, -16); \
|
||||
\
|
||||
C0 = fBlaMka(C0, D0); \
|
||||
C1 = fBlaMka(C1, D1); \
|
||||
\
|
||||
B0 = _mm_xor_si128(B0, C0); \
|
||||
B1 = _mm_xor_si128(B1, C1); \
|
||||
\
|
||||
B0 = _mm_roti_epi64(B0, -63); \
|
||||
B1 = _mm_roti_epi64(B1, -63); \
|
||||
} while ((void)0, 0)
|
||||
|
||||
#if defined(__SSSE3__)
|
||||
#define DIAGONALIZE(A0, B0, C0, D0, A1, B1, C1, D1) \
|
||||
do { \
|
||||
__m128i t0 = _mm_alignr_epi8(B1, B0, 8); \
|
||||
__m128i t1 = _mm_alignr_epi8(B0, B1, 8); \
|
||||
B0 = t0; \
|
||||
B1 = t1; \
|
||||
\
|
||||
t0 = C0; \
|
||||
C0 = C1; \
|
||||
C1 = t0; \
|
||||
\
|
||||
t0 = _mm_alignr_epi8(D1, D0, 8); \
|
||||
t1 = _mm_alignr_epi8(D0, D1, 8); \
|
||||
D0 = t1; \
|
||||
D1 = t0; \
|
||||
} while ((void)0, 0)
|
||||
|
||||
#define UNDIAGONALIZE(A0, B0, C0, D0, A1, B1, C1, D1) \
|
||||
do { \
|
||||
__m128i t0 = _mm_alignr_epi8(B0, B1, 8); \
|
||||
__m128i t1 = _mm_alignr_epi8(B1, B0, 8); \
|
||||
B0 = t0; \
|
||||
B1 = t1; \
|
||||
\
|
||||
t0 = C0; \
|
||||
C0 = C1; \
|
||||
C1 = t0; \
|
||||
\
|
||||
t0 = _mm_alignr_epi8(D0, D1, 8); \
|
||||
t1 = _mm_alignr_epi8(D1, D0, 8); \
|
||||
D0 = t1; \
|
||||
D1 = t0; \
|
||||
} while ((void)0, 0)
|
||||
#else /* SSE2 */
|
||||
#define DIAGONALIZE(A0, B0, C0, D0, A1, B1, C1, D1) \
|
||||
do { \
|
||||
__m128i t0 = D0; \
|
||||
__m128i t1 = B0; \
|
||||
D0 = C0; \
|
||||
C0 = C1; \
|
||||
C1 = D0; \
|
||||
D0 = _mm_unpackhi_epi64(D1, _mm_unpacklo_epi64(t0, t0)); \
|
||||
D1 = _mm_unpackhi_epi64(t0, _mm_unpacklo_epi64(D1, D1)); \
|
||||
B0 = _mm_unpackhi_epi64(B0, _mm_unpacklo_epi64(B1, B1)); \
|
||||
B1 = _mm_unpackhi_epi64(B1, _mm_unpacklo_epi64(t1, t1)); \
|
||||
} while ((void)0, 0)
|
||||
|
||||
#define UNDIAGONALIZE(A0, B0, C0, D0, A1, B1, C1, D1) \
|
||||
do { \
|
||||
__m128i t0, t1; \
|
||||
t0 = C0; \
|
||||
C0 = C1; \
|
||||
C1 = t0; \
|
||||
t0 = B0; \
|
||||
t1 = D0; \
|
||||
B0 = _mm_unpackhi_epi64(B1, _mm_unpacklo_epi64(B0, B0)); \
|
||||
B1 = _mm_unpackhi_epi64(t0, _mm_unpacklo_epi64(B1, B1)); \
|
||||
D0 = _mm_unpackhi_epi64(D0, _mm_unpacklo_epi64(D1, D1)); \
|
||||
D1 = _mm_unpackhi_epi64(D1, _mm_unpacklo_epi64(t1, t1)); \
|
||||
} while ((void)0, 0)
|
||||
#endif
|
||||
// BLAKE2_ROUND in reference code
|
||||
#define P(A0, A1, B0, B1, C0, C1, D0, D1) \
|
||||
do { \
|
||||
GB1(A0, B0, C0, D0, A1, B1, C1, D1); \
|
||||
GB2(A0, B0, C0, D0, A1, B1, C1, D1); \
|
||||
\
|
||||
DIAGONALIZE(A0, B0, C0, D0, A1, B1, C1, D1); \
|
||||
\
|
||||
GB1(A0, B0, C0, D0, A1, B1, C1, D1); \
|
||||
GB2(A0, B0, C0, D0, A1, B1, C1, D1); \
|
||||
\
|
||||
UNDIAGONALIZE(A0, B0, C0, D0, A1, B1, C1, D1); \
|
||||
} while ((void)0, 0)
|
||||
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE void xor(__m128i* out, __m128i* x, __m128i* y){
|
||||
for(uint8_t i = 0; i < 64; i++) { // ARGON2_BLOCK_SIZE (1024) / 16 bytes (128bits) = 64
|
||||
out[i] = _mm_xor_si128(x[i], y[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// G will be given uint64_t* values by JS, which can be automatically casted to _m128i*:
|
||||
// see https://stackoverflow.com/questions/11034302/sse-difference-between-mm-load-store-vs-using-direct-pointer-access
|
||||
EMSCRIPTEN_KEEPALIVE void G(__m128i* X, __m128i* Y, __m128i* R, __m128i* Z) {
|
||||
for (uint8_t i = 0; i < 64; i++) { // inlined `xor` to set both R and Z
|
||||
R[i] = Z[i] = _mm_xor_si128(X[i], Y[i]);
|
||||
}
|
||||
|
||||
for (uint8_t i = 0; i < 8; ++i) {
|
||||
P(Z[8 * i + 0], Z[8 * i + 1], Z[8 * i + 2],
|
||||
Z[8 * i + 3], Z[8 * i + 4], Z[8 * i + 5],
|
||||
Z[8 * i + 6], Z[8 * i + 7]);
|
||||
}
|
||||
|
||||
for (uint8_t i = 0; i < 8; ++i) {
|
||||
P(Z[8 * 0 + i], Z[8 * 1 + i], Z[8 * 2 + i],
|
||||
Z[8 * 3 + i], Z[8 * 4 + i], Z[8 * 5 + i],
|
||||
Z[8 * 6 + i], Z[8 * 7 + i]);
|
||||
}
|
||||
|
||||
xor(R, R, Z);
|
||||
}
|
||||
|
||||
// G^2
|
||||
EMSCRIPTEN_KEEPALIVE void G2(__m128i* X, __m128i* Y, __m128i* R, __m128i* Z) {
|
||||
G( X, Y, R, Z );
|
||||
G( X, R, R, Z );
|
||||
}
|
||||
|
||||
#else // no vectorization
|
||||
|
||||
uint64_t rotr64(uint64_t x, uint64_t n) { return (x >> n) ^ (x << (64 - n)); }
|
||||
|
||||
#define LSB(x) ((x) & 0xffffffff)
|
||||
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];}
|
||||
|
||||
|
||||
void GB(uint64_t* v, int a, int b, int c, int d) {
|
||||
|
||||
// a = (a + b + 2 * trunc(a) * trunc(b)) mod 2^(64)
|
||||
v[a] += v[b] + 2 * LSB(v[a]) * LSB(v[b]);
|
||||
|
||||
// d = (d XOR a) >>> 32, where >>> is a rotation
|
||||
v[d] = rotr64(v[d] ^ v[a], 32);
|
||||
// c = (c + d + 2 * trunc(c) * trunc(d)) mod 2^(64)
|
||||
v[c] += v[d] + 2 * LSB(v[c]) * LSB(v[d]);
|
||||
|
||||
// b = (b XOR c) >>> 24
|
||||
v[b] = rotr64(v[b] ^ v[c], 24);
|
||||
|
||||
// a = (a + b + 2 * trunc(a) * trunc(b)) mod 2^(64)
|
||||
v[a] += v[b] + 2 * LSB(v[a]) * LSB(v[b]);
|
||||
|
||||
|
||||
// d = (d XOR a) >>> 16
|
||||
v[d] = rotr64(v[d] ^ v[a], 16);
|
||||
|
||||
|
||||
// c = (c + d + 2 * trunc(c) * trunc(d)) mod 2^(64)
|
||||
v[c] += v[d] + 2 * LSB(v[c]) * LSB(v[d]);
|
||||
|
||||
|
||||
|
||||
// b = (b XOR c) >>> 63
|
||||
v[b] = rotr64(v[b] ^ v[c], 63);
|
||||
|
||||
}
|
||||
|
||||
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) {
|
||||
// v stores 16 64-bit values
|
||||
|
||||
GB(v, i0, i4, i8, i12);
|
||||
GB(v, i1, i5, i9, i13);
|
||||
GB(v, i2, i6, i10, i14);
|
||||
GB(v, i3, i7, i11, i15);
|
||||
|
||||
GB(v, i0, i5, i10, i15);
|
||||
GB(v, i1, i6, i11, i12);
|
||||
GB(v, i2, i7, i8, i13);
|
||||
GB(v, i3, i4, i9, i14);
|
||||
}
|
||||
// given a copy of R, compute Z (in-place)
|
||||
EMSCRIPTEN_KEEPALIVE void G(uint64_t* X, uint64_t* Y, uint64_t* R, uint64_t* Z) {
|
||||
xor(R, X, Y);
|
||||
// // we need to store S_i = (v_{2*i+1} || v_{2*i}), for v[i] of 64 bits
|
||||
// // S[0] = R[8:15] || R[0:7]
|
||||
for(uint8_t i = 0; i < 128; i+=16) {
|
||||
Z[i+0] = R[i+0]; Z[i+1] = R[i+1]; Z[i+2] = R[i+2]; Z[i+3] = R[i+3];
|
||||
Z[i+4] = R[i+4]; Z[i+5] = R[i+5]; Z[i+6] = R[i+6]; Z[i+7] = R[i+7];
|
||||
Z[i+8] = R[i+8]; Z[i+9] = R[i+9]; Z[i+10] = R[i+10]; Z[i+11] = R[i+11];
|
||||
Z[i+12] = R[i+12]; Z[i+13] = R[i+13]; Z[i+14] = R[i+14]; Z[i+15] = R[i+15];
|
||||
// const ids = [0, 1, 2, 3, 4, 5, 6, 7].map(j => i*128 + j*16); // 0, 16, .. 112 | 128, 144...
|
||||
// ( Q_0, Q_1, Q_2, ... , Q_7) <- P( R_0, R_1, R_2, ... , R_7) of 16-bytes each
|
||||
P(Z,i+0, i+1, i+2, i+3,
|
||||
i+4, i+5, i+6, i+7,
|
||||
i+8, i+9, i+10, i+11,
|
||||
i+12, i+13, i+14, i+15);
|
||||
}
|
||||
|
||||
for(uint8_t i = 0; i < 16; i+=2) {
|
||||
// Q_0 = Q[8:15] || Q[0:7]
|
||||
// const ids = [0, 1, 2, 3, 4, 5, 6, 7].map(j => i*16 + j*128); // 128 .. 896 | 16, 144 .. 912 | ..
|
||||
// ( Z_0, Z_8, Z_16, ... , Z_56) <- P( Q_0, Q_8, Q_16, ... , Q_56) of 16-bytes each
|
||||
// ( Z_1, Z_9, Z_17, ... , Z_57) <- P( Q_1, Q_9, Q_17, ... , Q_57) ...
|
||||
P(Z, i+0, i+1, i+16, i+17,
|
||||
i+32, i+33, i+48, i+49,
|
||||
i+64, i+65, i+80, i+81,
|
||||
i+96, i+97, i+112, i+113); // store one column of Z at a time
|
||||
}
|
||||
|
||||
xor(R, R, Z);
|
||||
|
||||
}
|
||||
|
||||
// G^2
|
||||
EMSCRIPTEN_KEEPALIVE void G2(uint64_t* X, uint64_t* Y, uint64_t* R, uint64_t* Z) {
|
||||
G( X, Y, R, Z );
|
||||
G( X, R, R, Z );
|
||||
}
|
||||
#endif
|
||||
|
||||
// Returns out = [l, z]
|
||||
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) {
|
||||
// For the first pass (r=0) and the first slice (sl=0), the block is taken from the current lane.
|
||||
uint32_t l = (pass == 0 && slice == 0) ? currentLane : J1J2[1] % p;
|
||||
|
||||
// W includes the indices of all blocks in the last SL - 1 = 3 segments computed and finished (possibly from previous pass, if any).
|
||||
// Plus, if `l` is on the current lane, we can also reference the finished blocks in the current segment (up to 'offset')
|
||||
uint32_t offset = l == currentLane
|
||||
? segmentOffset - 1
|
||||
: segmentOffset == 0 ? -1 : 0; // If B[i][j] is the first block of a segment, then the very last index from W is excluded.
|
||||
uint32_t segmentCount = pass == 0 ? slice : SL-1;
|
||||
uint64_t W_area = segmentCount * segmentLength + offset;
|
||||
// cast to uint64_t since we don't want the multiplication to be in uint32_t space
|
||||
uint32_t x = ((uint64_t)J1J2[0] * J1J2[0]) >> 32;
|
||||
uint32_t y = (W_area * x) >> 32;
|
||||
uint32_t zz = W_area - 1 - y;
|
||||
uint32_t startPos = pass == 0 ? 0 : (slice + 1) * segmentLength; // next segment (except for first pass)
|
||||
// TODO (?) possible optimisation: zz < 2 * (SL * segmentLength) so we can use an if instead of %
|
||||
uint32_t z = (startPos + zz) % (SL * segmentLength);
|
||||
out[0] = l;
|
||||
out[1] = z;
|
||||
return out;
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"name": "argon2id",
|
||||
"version": "1.0.1",
|
||||
"description": "Argon2id implementation in pure Javascript",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
"types": "index.d.ts",
|
||||
"files": [
|
||||
"dist/",
|
||||
"lib/",
|
||||
"index.d.ts"
|
||||
],
|
||||
"scripts": {
|
||||
"test": "mocha --loader=ts-node/esm test/blake2b.spec.js test/argon2id.spec.ts",
|
||||
"lint": "eslint index.js lib test",
|
||||
"build": "rm -rf dist && mkdir dist && ./build-wasm.sh",
|
||||
"test-browser": "karma start karma.config.cjs",
|
||||
"preversion": "npm run build && npm test"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/openpgpjs/argon2id.git"
|
||||
},
|
||||
"keywords": [
|
||||
"argon2",
|
||||
"argon2id",
|
||||
"rfc9106"
|
||||
],
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/chai": "^4.3.4",
|
||||
"@types/mocha": "^10.0.1",
|
||||
"@typescript-eslint/eslint-plugin": "^5.51.0",
|
||||
"@typescript-eslint/parser": "^5.51.0",
|
||||
"chai": "^4.3.7",
|
||||
"eslint": "^8.32.0",
|
||||
"eslint-plugin-import": "^2.27.5",
|
||||
"karma": "^6.4.1",
|
||||
"karma-browserstack-launcher": "^1.6.0",
|
||||
"karma-chrome-launcher": "^3.1.1",
|
||||
"karma-firefox-launcher": "^2.1.2",
|
||||
"karma-mocha": "^2.0.1",
|
||||
"karma-mocha-reporter": "^2.2.5",
|
||||
"karma-webkit-launcher": "^2.1.0",
|
||||
"karma-webpack": "^5.0.0",
|
||||
"mocha": "^10.2.0",
|
||||
"playwright": "^1.30.0",
|
||||
"string-replace-loader": "^3.1.0",
|
||||
"ts-loader": "^9.4.2",
|
||||
"ts-node": "^10.9.1",
|
||||
"wasm-loader": "^1.3.0",
|
||||
"webpack": "^5.75.0",
|
||||
"webpack-cli": "^5.0.1"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user