From a6437169c36fb2ab39b500e663b84bbf288edb02 Mon Sep 17 00:00:00 2001 From: Chris Duncan Date: Sat, 18 Jul 2026 00:29:31 -0700 Subject: [PATCH] Document BLAKE2b code gen script. --- scripts/blake2b-gen.js | 134 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 118 insertions(+), 16 deletions(-) diff --git a/scripts/blake2b-gen.js b/scripts/blake2b-gen.js index c8070ac..5df40c8 100644 --- a/scripts/blake2b-gen.js +++ b/scripts/blake2b-gen.js @@ -3,13 +3,35 @@ import { writeFile } from 'node:fs/promises' +/** + * Generate the BLAKE2b hashing algorithm for different Web APIs. Specifically, + * the output varies by API as follows: + * + * - WebGPU: outputs `compute.wgsl`, a compute shader + * - WebGL: outputs `draw.frag`, a fragment shader + * - WASM: outputs `asm/index.ts`, an AssemblyScript file that must be compiled + * to a WASM module + * + * Some whitespace output is removed for brevity in the final files, which + * should be checked separately for correctness using language-specific tools + * e.g. `wgsl-analyzer` or `asc` + * + * @param {('wasm'|'webgl'|'webgpu')} api + * @returns {string} + */ function generate (api) { if (api !== 'wasm' && api !== 'webgl' && api !== 'webgpu') { throw new TypeError(`Cannot generate BLAKE2b code for ${api}`) } - // Shorthand for adding two operands and storing sum in the first. + /** + * Shorthand for adding two operands and storing sum in the first. + * + * @param {string} a - first operand variable name, and target for result + * @param {string} b - second operand variable name + * @returns {string} API-specific code for addition assignment + */ function add (a, b) { switch (api) { case 'wasm': { @@ -32,7 +54,18 @@ function generate (api) { } } - // Shorthand for how values are constructed. + /** + * Shorthand for how values are constructed. For WASM, the 64-bit uint is + * copied into two lanes of a 128-bit vector in order to use them in parallel + * with SIMD. For WebGL and WebGPU, which do not have native 64-bit uints, the + * 64-bit integer is split into 32-bit values representing its high and low + * bits and then assigned to two lanes of a vector; see `add()` for the extra + * carry bit step required for this representation. + * + * @param {string} lo - low 32 bits of a 64-bit integer + * @param {string} [hi] - high 32 bits of a 64-bit integer + * @returns {string} API-specific code for initializing the full 64-bit value + */ function ctr (lo, hi) { switch (api) { case 'wasm': { @@ -50,7 +83,11 @@ function generate (api) { } } - // Shorthand for declaration statement. + /** + * Shorthand for declaration statement. + * + * @returns {('let'|'uvec2'|'var')} + */ const declare = (() => { switch (api) { case 'wasm': return 'let' @@ -59,7 +96,13 @@ function generate (api) { } })() - // Shorthand for righthand bit rotation of a SIMD i64x2 value. + /** + * Shorthand for righthand bit rotation of a SIMD vector of 64-bit uints. + * + * @param {string} v - variable to rotate + * @param {number} i - number of bits to rotate + * @returns {string} API-specific code for bit-rotating the integer + */ function rotr (v, i) { switch (api) { case 'wasm': { @@ -97,7 +140,13 @@ function generate (api) { } } - // Shorthand for XOR'ing two SIMD i64x2 operands and saving result in the first. + /** + * Shorthand for XOR'ing two SIMD i64x2 operands and assigning to the first. + * + * @param {string} a - first operand variable name, and target for result + * @param {string} b - second operand variable name + * @returns {string} API-specific code for xor assignment + */ function xor (a, b) { switch (api) { case 'wasm': return `${a} = v128.xor(${a}, ${b});` @@ -132,11 +181,14 @@ function generate (api) { [14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3] ] - // Initialization vector defined by BLAKE2. - // Application of each XOR is defined by BLAKE2 section 2.4 compression - // function. Each value represents two halves of the original u64 value from the - // reference implementation. They appear reversed pairwise in order to align - // with little-endian computation. + /** + * Initialization vector defined by BLAKE2. + * + * Application of each XOR is defined by BLAKE2 section 2.4 compression + * function. Each value represents two halves of the original u64 value from + * the reference implementation. They appear reversed pairwise in order to + * align with little-endian computation. + */ const blake2b_iv = [ ctr(`0xf3bcc908`, `0x6a09e667`), ctr(`0x84caa73b`, `0xbb67ae85`), @@ -148,18 +200,39 @@ function generate (api) { ctr(`0x137e2179`, `0x5be0cd19`) ] - // Parameter block as defined in BLAKE2 section 2.8 and configured as follows: - // maximal depth = 1, fanout = 1, digest byte length = 8 + /** + * Parameter block as defined in BLAKE2 section 2.8 and configured as follows: + * maximal depth = 1, fanout = 1, digest byte length = 8 + */ const blake2b_param = ctr(`0x01010008`, `0x0`) - // Message input length which is always 40 for Nano. - // 8 nonce bytes + 32 block hash bytes + /** + * Message input length which is always 40 for Nano. + * 8 nonce bytes + 32 block hash bytes + */ const blake2b_inlen = ctr(`0x28`, `0x0`) - // Finalization flag as defined in BLAKE2 section 2.4 and set to ~0 since this - // is the final (and only) message block being hashed. + /** + * Finalization flag as defined in BLAKE2 section 2.4 and set to ~0 since this + * is the final (and only) message block being hashed. + */ const blake2b_final = ctr(`~0x0`) + /** + * Mixing function as defined in BLAKE2. + * + * Specifically skips addition of message input past m[4] since input for + * Nano proof-of-work is always precisely 40 bytes and any addition past that + * point would just be adding zero. + * + * @param {number} a - G mixing index + * @param {number} b - G mixing index + * @param {number} c - G mixing index + * @param {number} d - G mixing index + * @param {number} x - SIGMA index for first message block + * @param {number} y - SIGMA index for second message block + * @returns {string} Unrolled code for a pass of columnar and diagonal mixing + */ function G (a, b, c, d, x, y) { return ` ${add(`v${a}`, `v${b}`)} @@ -179,6 +252,15 @@ function generate (api) { ` } + /** + * Builds string of code for calling G mixing function as defined by BLAKE2. + * + * Specifically skips passes 6 and 8 during the final round since they do not + * affect the final output when used for Nano proof-of-work. + * + * @param {number} r + * @returns {string} Unrolled code for 8 passes of G + */ function ROUND (r) { let output = ` // ROUND ${r} @@ -193,6 +275,11 @@ function generate (api) { return output } + /** + * Builds string of code for initializing state vectors. Loops are unrolled. + * + * @returns {string} API-specific code setting initial state + */ function INIT () { let output = ` // INITIALIZE STATE VECTOR @@ -213,6 +300,13 @@ function generate (api) { ` } + /** + * Builds string of code that first initializes the BLAKE2b state vectors and + * then compresses the input message. For Nano proof-of-work, the input is + * only 40 bytes, so compression only happens once. + * + * @returns {string} API-specific code executing 12 rounds of G mixing + */ function COMPRESS () { let output = ` ${INIT()} @@ -224,6 +318,14 @@ function generate (api) { return output.replace(/\t{2,}/g, '\t') } + /** + * Builds string of code that represents the entire BLAKE2b hashing algorithm, + * optimized for Nano proof-of-work, based on a particular Web API. This code + * in general is not shared between APIs and thus cannot be abstracted into + * builder functions like other code. + * + * @returns {string} API-specific code to search for Nano proof-of-work values + */ const main = (() => { switch (api) { case 'wasm': { -- 2.52.0