Skip to content

Commit

Permalink
Add more comments
Browse files Browse the repository at this point in the history
  • Loading branch information
paulmillr committed Jan 14, 2025
1 parent 2976384 commit 2dd1f53
Show file tree
Hide file tree
Showing 13 changed files with 72 additions and 51 deletions.
10 changes: 9 additions & 1 deletion src/_assert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,38 +3,46 @@
* @module
*/

/** Asserts something is positive integer. */
function anumber(n: number): void {
if (!Number.isSafeInteger(n) || n < 0) throw new Error('positive integer expected, got ' + n);
}

// copied from utils
/** Is number an Uint8Array? Copied from utils for perf. */
function isBytes(a: unknown): a is Uint8Array {
return a instanceof Uint8Array || (ArrayBuffer.isView(a) && a.constructor.name === 'Uint8Array');
}

/** Asserts something is Uint8Array. */
function abytes(b: Uint8Array | undefined, ...lengths: number[]): void {
if (!isBytes(b)) throw new Error('Uint8Array expected');
if (lengths.length > 0 && !lengths.includes(b.length))
throw new Error('Uint8Array expected of length ' + lengths + ', got length=' + b.length);
}

/** Hash interface. */
export type Hash = {
(data: Uint8Array): Uint8Array;
blockLen: number;
outputLen: number;
create: any;
};

/** Asserts something is hash */
function ahash(h: Hash): void {
if (typeof h !== 'function' || typeof h.create !== 'function')
throw new Error('Hash should be wrapped by utils.wrapConstructor');
anumber(h.outputLen);
anumber(h.blockLen);
}

/** Asserts a hash instance has not been destroyed / finished */
function aexists(instance: any, checkFinished = true): void {
if (instance.destroyed) throw new Error('Hash instance has been destroyed');
if (checkFinished && instance.finished) throw new Error('Hash#digest() has already been called');
}

/** Asserts output is properly-sized byte array */
function aoutput(out: any, instance: any): void {
abytes(out);
const min = instance.outputLen;
Expand Down
20 changes: 9 additions & 11 deletions src/_md.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@
import { aexists, aoutput } from './_assert.js';
import { Hash, createView, Input, toBytes } from './utils.js';

/**
* Polyfill for Safari 14
*/
/** Polyfill for Safari 14. https://caniuse.com/mdn-javascript_builtins_dataview_setbiguint64 */
export function setBigUint64(
view: DataView,
byteOffset: number,
Expand All @@ -25,15 +23,15 @@ export function setBigUint64(
view.setUint32(byteOffset + l, wl, isLE);
}

/**
* Choice: a ? b : c
*/
export const Chi = (a: number, b: number, c: number): number => (a & b) ^ (~a & c);
/** Choice: a ? b : c */
export function Chi(a: number, b: number, c: number): number {
return (a & b) ^ (~a & c);
}

/**
* Majority function, true if any two inputs is true
*/
export const Maj = (a: number, b: number, c: number): number => (a & b) ^ (a & c) ^ (b & c);
/** Majority function, true if any two inputs is true. */
export function Maj(a: number, b: number, c: number): number {
return (a & b) ^ (a & c) ^ (b & c);
}

/**
* Merkle-Damgard hash construction base class.
Expand Down
2 changes: 1 addition & 1 deletion src/_u64.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* BigUint64Array is too slow as per 2024, so we implement it using Uint32Array.
* Internal helpers for u64. BigUint64Array is too slow as per 2025, so we implement it using Uint32Array.
* @todo re-check https://issues.chromium.org/issues/42212588
* @module
*/
Expand Down
2 changes: 1 addition & 1 deletion src/argon2.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Argon2 from RFC 9106.
* Argon2 KDF from RFC 9106. Can be used to create a key from password and salt.
* We suggest to use Scrypt. JS Argon is 2-10x slower than native code because of 64-bitness:
* * argon uses uint64, but JS doesn't have fast uint64array
* * uint64 multiplication is 1/3 of time
Expand Down
4 changes: 2 additions & 2 deletions src/blake2b.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Blake2b fast hash, focusing on 64-bit platforms.
* Blake2b hash function. Focuses on 64-bit platforms, but in JS speed different from Blake2s is negligible.
* @module
*/
import { BLAKE, BlakeOpts, SIGMA } from './_blake.js';
Expand Down Expand Up @@ -200,7 +200,7 @@ export class BLAKE2b extends BLAKE<BLAKE2b> {
}

/**
* BLAKE2b - optimized for 64-bit platforms. JS doesn't have uint64, so it's slower than BLAKE2s.
* Blake2b hash function. Focuses on 64-bit platforms, but in JS speed different from Blake2s is negligible.
* @param msg - message that would be hashed
* @param opts - dkLen output length, key for MAC mode, salt, personalization
*/
Expand Down
10 changes: 6 additions & 4 deletions src/blake2s.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
/**
* Blake2s fast hash, focusing on 8-bit to 32-bit platforms.
* Blake2s hash function. Focuses on 8-bit to 32-bit platforms. blake2b for 64-bit, but in JS it is slower.
* @module
*/
import { BLAKE, BlakeOpts, SIGMA } from './_blake.js';
import { fromBig } from './_u64.js';
import { CHashO, rotr, toBytes, wrapConstructorWithOpts, u32, byteSwapIfBE } from './utils.js';

// Initial state: same as SHA256
// first 32 bits of the fractional parts of the square roots of the first 8 primes 2..19
/**
* Initial state: same as SHA256. First 32 bits of the fractional parts of the square roots
* of the first 8 primes 2..19.
*/
// prettier-ignore
export const B2S_IV: Uint32Array = /* @__PURE__ */ new Uint32Array([
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19
Expand Down Expand Up @@ -143,7 +145,7 @@ export class BLAKE2s extends BLAKE<BLAKE2s> {
}

/**
* BLAKE2s - optimized for 32-bit platforms. JS doesn't have uint64, so it's faster than BLAKE2b.
* Blake2s hash function. Focuses on 8-bit to 32-bit platforms. blake2b for 64-bit, but in JS it is slower.
* @param msg - message that would be hashed
* @param opts - dkLen output length, key for MAC mode, salt, personalization
*/
Expand Down
5 changes: 2 additions & 3 deletions src/blake3.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@
* Why is this so slow? While it should be 6x faster than blake2b, perf diff is only 20%:
*
* * There is only 30% reduction in number of rounds from blake2s
* * Speed-up comes from tree structure,
* which is parallelized using SIMD & threading. These features are not present in JS,
* so we only get overhead from trees.
* * Speed-up comes from tree structure, which is parallelized using SIMD & threading.
* These features are not present in JS, so we only get overhead from trees.
* * Parallelization only happens on 1024-byte chunks: there is no benefit for small inputs.
* * It is still possible to make it faster using: a) loop unrolling b) web workers c) wasm
* @module
Expand Down
2 changes: 1 addition & 1 deletion src/pbkdf2.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* PBKDF (RFC 2898).
* PBKDF (RFC 2898). Can be used to create a key from password and salt.
* @module
*/
import { ahash, anumber } from './_assert.js';
Expand Down
2 changes: 1 addition & 1 deletion src/scrypt.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* RFC 7914 Scrypt KDF.
* RFC 7914 Scrypt KDF. Can be used to create a key from password and salt.
* @module
*/
import { anumber } from './_assert.js';
Expand Down
2 changes: 1 addition & 1 deletion src/sha2.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* SHA2. A.k.a. sha256, sha512, sha512_256, etc.
* SHA2 hash function. A.k.a. sha256, sha512, sha512_256, etc.
* @module
*/
// Usually you either use sha256, or sha512. We re-export them as sha2 for naming consistency.
Expand Down
4 changes: 2 additions & 2 deletions src/sha3-addons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
*
* * Full [NIST SP 800-185](https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-185.pdf):
* cSHAKE, KMAC, TupleHash, ParallelHash + XOF variants
* * [Reduced-round Keccak](https://datatracker.ietf.org/doc/draft-irtf-cfrg-kangarootwelve/):
* * Reduced-round Keccak [(draft)](https://datatracker.ietf.org/doc/draft-irtf-cfrg-kangarootwelve/):
* * 🦘 K12 aka KangarooTwelve
* * M14 aka MarsupilamiFourteen
* * TurboSHAKE
* * [KeccakPRG](https://keccak.team/files/CSF-0.1.pdf): Pseudo-random generator based on Keccak
* * KeccakPRG: Pseudo-random generator based on Keccak [(pdf)](https://keccak.team/files/CSF-0.1.pdf)
* @module
*/
import { anumber } from './_assert.js';
Expand Down
2 changes: 2 additions & 0 deletions src/sha3.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
* Check out [FIPS-202](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.202.pdf),
* [Website](https://keccak.team/keccak.html),
* [the differences between SHA-3 and Keccak](https://crypto.stackexchange.com/questions/15727/what-are-the-key-differences-between-the-draft-sha-3-standard-and-the-keccak-sub).
*
* Check out `sha3-addons` module for cSHAKE, k12, and others.
* @module
*/
import { abytes, aexists, anumber, aoutput } from './_assert.js';
Expand Down
58 changes: 35 additions & 23 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,31 +23,39 @@ export type TypedArray = Int8Array | Uint8ClampedArray | Uint8Array |
Uint16Array | Int16Array | Uint32Array | Int32Array;

// Cast array to different type
export const u8 = (arr: TypedArray): Uint8Array =>
new Uint8Array(arr.buffer, arr.byteOffset, arr.byteLength);
export const u32 = (arr: TypedArray): Uint32Array =>
new Uint32Array(arr.buffer, arr.byteOffset, Math.floor(arr.byteLength / 4));
export function u8(arr: TypedArray): Uint8Array {
return new Uint8Array(arr.buffer, arr.byteOffset, arr.byteLength);
}
export function u32(arr: TypedArray): Uint32Array {
return new Uint32Array(arr.buffer, arr.byteOffset, Math.floor(arr.byteLength / 4));
}

// Cast array to view
export const createView = (arr: TypedArray): DataView =>
new DataView(arr.buffer, arr.byteOffset, arr.byteLength);
export function createView(arr: TypedArray): DataView {
return new DataView(arr.buffer, arr.byteOffset, arr.byteLength);
}

/** The rotate right (circular right shift) operation for uint32 */
export const rotr = (word: number, shift: number): number =>
(word << (32 - shift)) | (word >>> shift);
export function rotr(word: number, shift: number): number {
return (word << (32 - shift)) | (word >>> shift);
}
/** The rotate left (circular left shift) operation for uint32 */
export const rotl = (word: number, shift: number): number =>
(word << shift) | ((word >>> (32 - shift)) >>> 0);
export function rotl(word: number, shift: number): number {
return (word << shift) | ((word >>> (32 - shift)) >>> 0);
}

/** Is current platform little-endian? Most are. Big-Endian platform: IBM */
export const isLE: boolean = /* @__PURE__ */ (() =>
new Uint8Array(new Uint32Array([0x11223344]).buffer)[0] === 0x44)();
// The byte swap operation for uint32
export const byteSwap = (word: number): number =>
((word << 24) & 0xff000000) |
((word << 8) & 0xff0000) |
((word >>> 8) & 0xff00) |
((word >>> 24) & 0xff);
export function byteSwap(word: number): number {
return (
((word << 24) & 0xff000000) |
((word << 8) & 0xff0000) |
((word >>> 8) & 0xff00) |
((word >>> 24) & 0xff)
);
}
/** Conditionally byte swap if on a big-endian platform */
export const byteSwapIfBE: (n: number) => number = isLE
? (n: number) => n
Expand Down Expand Up @@ -109,12 +117,14 @@ export function hexToBytes(hex: string): Uint8Array {
return array;
}

// There is no setImmediate in browser and setTimeout is slow.
// call of async fn will return Promise, which will be fullfiled only on
// next scheduler queue processing step and this is exactly what we need.
/**
* There is no setImmediate in browser and setTimeout is slow.
* Call of async fn will return Promise, which will be fullfiled only on
* next scheduler queue processing step and this is exactly what we need.
*/
export const nextTick = async (): Promise<void> => {};

// Returns control to thread each 'tick' ms to avoid blocking
/** Returns control to thread each 'tick' ms to avoid blocking. */
export async function asyncLoop(
iters: number,
tick: number,
Expand Down Expand Up @@ -176,7 +186,7 @@ export function concatBytes(...arrays: Uint8Array[]): Uint8Array {
return res;
}

// For runtime check if class implements interface
/** For runtime check if class implements interface */
export abstract class Hash<T extends Hash<T>> {
abstract blockLen: number; // Bytes per block
abstract outputLen: number; // Bytes in output
Expand Down Expand Up @@ -226,10 +236,14 @@ export function checkOpts<T1 extends EmptyObj, T2 extends EmptyObj>(
return merged as T1 & T2;
}

/** Hash function */
export type CHash = ReturnType<typeof wrapConstructor>;
/** Hash function with output */
export type CHashO = ReturnType<typeof wrapConstructorWithOpts>;
/** XOF with output */
export type CHashXO = ReturnType<typeof wrapXOFConstructorWithOpts>;

/** Wraps hash function, creating an interface on top of it */
export function wrapConstructor<T extends Hash<T>>(
hashCons: () => Hash<T>
): {
Expand Down Expand Up @@ -278,9 +292,7 @@ export function wrapXOFConstructorWithOpts<H extends HashXOF<H>, T extends Objec
return hashC;
}

/**
* Secure PRNG. Uses `crypto.getRandomValues`, which defers to OS.
*/
/** Cryptographically secure PRNG. Uses internal OS-level `crypto.getRandomValues`. */
export function randomBytes(bytesLength = 32): Uint8Array {
if (crypto && typeof crypto.getRandomValues === 'function') {
return crypto.getRandomValues(new Uint8Array(bytesLength));
Expand Down

0 comments on commit 2dd1f53

Please sign in to comment.