]> git.codecow.com Git - nano-pow.git/commitdiff
Document BLAKE2b code gen script.
authorChris Duncan <chris@codecow.com>
Sat, 18 Jul 2026 07:29:31 +0000 (00:29 -0700)
committerChris Duncan <chris@codecow.com>
Sun, 19 Jul 2026 07:18:06 +0000 (00:18 -0700)
scripts/blake2b-gen.js

index c8070ac1ba9192fd8d98dfb50848004e91744d51..5df40c8924e3e3199b4be49a6b6d2c92c2e7fddd 100644 (file)
@@ -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': {