From: Chris Duncan Date: Thu, 24 Sep 2026 19:57:08 +0000 (-0700) Subject: Generate blake hashing to unroll long loops. X-Git-Url: https://git.codecow.com/?a=commitdiff_plain;h=826e154cb74ad25803061603baad2f63b3365c36;p=nano25519.git Generate blake hashing to unroll long loops. --- diff --git a/esbuild/config.mjs b/esbuild/config.mjs index e5e5f32..64f8198 100644 --- a/esbuild/config.mjs +++ b/esbuild/config.mjs @@ -10,9 +10,9 @@ build({ outfile: 'test/vectors.mjs', banner: { js: `/** -* This file is automatically generated during the build process. -* Add or modify test vectors from the files in the test/vectors directory. -*/` + * This file is automatically generated during the build process. + * Add or modify test vectors from the files in the test/vectors directory. + */` } }) diff --git a/package.json b/package.json index 6119d47..ce4c013 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "build": "npm run clean && npm run compile && node ./esbuild/dev.mjs", "build:prod": "npm run clean && npm run compile && node ./esbuild/prod.mjs", "clean": "rm -rf {build,dist}", - "compile": "asc ./src/assembly/index.ts && tsc", + "compile": "node scripts/blake2b-gen.mjs && asc ./src/assembly/index.ts && tsc", "prepublishOnly": "npm run test:prod", "test": "npm run build && node ./test/node.mjs", "test:prod": "npm run build:prod && node ./test/node.mjs" diff --git a/scripts/blake2b-gen.mjs b/scripts/blake2b-gen.mjs new file mode 100644 index 0000000..f0bb7f4 --- /dev/null +++ b/scripts/blake2b-gen.mjs @@ -0,0 +1,300 @@ +//! SPDX-FileCopyrightText: 2026 Chris Duncan +//! SPDX-License-Identifier: GPL-3.0-or-later + +/** + * Generate the BLAKE2b hashing algorithm for WebAssembly API. Specifically, it + * outputs `asm/index.ts`, an AssemblyScript file that must be compiled as part + * of 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` + */ + +import { writeFile } from 'node:fs/promises' + +const blake2b_state = [ + [0, 4, 8, 12], + [1, 5, 9, 13], + [2, 6, 10, 14], + [3, 7, 11, 15], + [0, 5, 10, 15], + [1, 6, 11, 12], + [2, 7, 8, 13], + [3, 4, 9, 14] +] + +const blake2b_sigma = [ + [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] +] + +/** + * 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 = [ + `0x6a09e667f3bcc908`, + `0xbb67ae8584caa73b`, + `0x3c6ef372fe94f82b`, + `0xa54ff53a5f1d36f1`, + `0x510e527fade682d1`, + `0x9b05688c2b3e6c1f`, + `0x1f83d9abfb41bd6b`, + `0x5be0cd19137e2179` +] + +const B_LENGTH = 128 +const H_LENGTH = 8 + +const DECLARATIONS = (() => { + let output = ` + /** + * Buffers and counters + * - b: input buffer, filled and refilled by input data in ${B_LENGTH} byte increments + * - c: buffer counter, points to current index in \`b\` + * - h: chain buffer, updated after each compression + * - p: set according to BLAKE2 layout from constructor arguments + * - t: total byte counter, incremented by \`c\` + * - Note: BLAKE2b reference supports 2¹²⁸-1, but this implementation restricts to 2⁶⁴-1 for compatibility with WASM without SIMD) + */ + b: StaticArray = new StaticArray(${B_LENGTH}) + c: i32 = 0 + h: StaticArray = new StaticArray(${H_LENGTH}) + p: StaticArray = new StaticArray(64) + t: u64 = 0 + + ${COMPRESS()} + ` + return output +})() + +function CLEAR () { + let output = ` + this.b.fill(0) + this.c = 0 + this.h.fill(0) + this.p.fill(0) + this.t = 0 + ` + + return output +} + +const INIT = (() => { + let output = ` + init(): Blake2b { + // reset buffers and counters + ${CLEAR()} + + // initialize parameter block + this.p[0] = 64 // always 64 bytes for this implementation + this.p[1] = 0 // no key + this.p[2] = 1 // fanout + this.p[3] = 1 // depth + ` + + output += ` + // initialize hash state + ` + for (let i = 0; i < H_LENGTH; i++) { + output += ` + this.h[${i}] = ${blake2b_iv[i]} + ` + } + + output += ` + for (let i = 0; i < ${H_LENGTH}; i++) { + this.h[i] ^= load(changetype(this.p) + (i << 3)) + } + return this + } + ` + + return output +})() + +/** + * Mixing function as defined in BLAKE2. + * + * @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 ` + v${a} = v${a} + v${b} + m${x} + v${d} = rotr(v${d} ^ v${a}, 32) + v${c} = v${c} + v${d} + v${b} = rotr(v${b} ^ v${c}, 24) + v${a} = v${a} + v${b} + m${y} + v${d} = rotr(v${d} ^ v${a}, 16) + v${c} = v${c} + v${d} + v${b} = rotr(v${b} ^ v${c}, 63) + ` +} + +/** + * Builds string of code for calling G mixing function as defined by BLAKE2. + * + * @param {number} r + * @returns {string} Unrolled code for 8 passes of G + */ +function ROUND (r) { + let output = ` + // ROUND ${r} + ` + for (let i = 0; i < 8; i++) { + const a = blake2b_state[i][0] + const b = blake2b_state[i][1] + const c = blake2b_state[i][2] + const d = blake2b_state[i][3] + const s = blake2b_sigma[r] + output += G(a, b, c, d, s[i << 1], s[(i << 1) + 1]) + } + return output +} + +/** + * 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 = ` + // COMPRESS + // Defined in BLAKE2 section 2.4 + compress (isFinal: u64): void { + // initialize compression buffers + // - m: message buffer, represents current state of \`b\` during compression + // - v: state vector, set to bytes of \`h\` and \`iv\` then modified by \`t\` during compression + ` + for (let i = 0; i < H_LENGTH; i++) { + output += ` + let v${i}: u64 = this.h[${i}] + let v${i + H_LENGTH}: u64 = ${blake2b_iv[i]} + ` + } + + output += ` + // lo 64 bits of counter + v${12} ^= this.t + + // hi 64 bits of counter, always 0 in this implementation + v${13} ^= 0 + + // flip bits if "last block" flag is set so that v14 = ~v14 + v${14} ^= isFinal + + // copy input buffer to message block + const b_ptr = changetype(this.b) + ` + + for (let i = 0; i < 16; i++) { + output += ` + let m${i}: u64 = load(b_ptr + ${i << 3}) + ` + } + + output += ` + // twelve rounds of mixing + ` + for (let r = 0; r < 12; r++) { + output += ROUND(r) + } + + output += ` + // set new chain value + ` + for (let i = 0; i < H_LENGTH; i++) { + output += ` + this.h[${i}] ^= v${i} ^ v${i + H_LENGTH} + ` + } + + output += ` + } + ` + return output +} + +const BLAKE2b = `//! SPDX-FileCopyrightText: 2026 Chris Duncan +//! SPDX-License-Identifier: GPL-3.0-or-later + +export class Blake2b { + ${DECLARATIONS} + ${INIT} + // input: variable-length message bytes passed by user to be hashed + update (input: StaticArray, length: i32): Blake2b { + const input_ptr: usize = changetype(input) + const b_ptr: usize = changetype(this.b) + + let i = 0; + while (i < length) { + + // is buffer full? + if (this.c === ${B_LENGTH}) { + + // increment total byte counter + this.t += ${B_LENGTH} + + // reset buffer counter to zero + this.c = 0 + + // compress (not final) + this.compress(0) + + } else { + const b_rem = ${B_LENGTH} - this.c + const l_rem = length - i + const n = b_rem < l_rem ? b_rem : l_rem + memory.copy(b_ptr + this.c, input_ptr + i, n) + this.c += n + i += n + } + } + return this + } + + // output: 64-byte hash of original message input + digest (output: StaticArray): void { + + // add final message block size to total bytes + this.t += this.c + + // pad final block with zeros + this.b.fill(0, this.c) + + // set final block flag and compress + this.compress(u64(0 - 1)) + + // return byte array of 64-byte chain buffer + memory.copy(changetype(output), changetype(this.h), 64) + + // clear internal buffers and counters + ${CLEAR()} + } +} +` + +await writeFile('src/assembly/ed25519/blake2b.ts', BLAKE2b) diff --git a/src/assembly/crypto_sign.ts b/src/assembly/crypto_sign.ts index 064e493..8ed0acc 100644 --- a/src/assembly/crypto_sign.ts +++ b/src/assembly/crypto_sign.ts @@ -2,7 +2,6 @@ //! SPDX-License-Identifier: GPL-3.0-or-later import { KEY_BYTELENGTH } from './constants' -import { crypto_derive } from './crypto_derive' import { Blake2b } from './ed25519/blake2b' import { ge_scalarmult_base_tobytes } from './ed25519/ge' import { sc_muladd, sc_reduce } from './ed25519/sc' @@ -40,14 +39,15 @@ const S = new StaticArray(64) * @param {StaticArray} pub 32-byte public key from input buffer */ export function crypto_sign (RS: StaticArray, M: StaticArray, mlen: i32, prv: StaticArray, pub: StaticArray): i32 { - // Derive `A` from private key - crypto_derive(A, prv) - let c = 0 - for (let i = 0; i < KEY_BYTELENGTH; i++) { - c |= A[i] ^ pub[i] - } - // Return early if `A` does not match public key - if (c != 0) return -1 + // // Derive `A` from private key + // crypto_derive(A, prv) + // let c = 0 + // for (let i = 0; i < KEY_BYTELENGTH; i++) { + // c |= A[i] ^ pub[i] + // } + // // Return early if `A` does not match public key + // if (c != 0) return c + memory.copy(changetype(A), changetype(pub), 32) // Hash private key to `h` blake2b.init().update(prv, KEY_BYTELENGTH).digest(h) diff --git a/src/assembly/ed25519/blake2b.ts b/src/assembly/ed25519/blake2b.ts index eb380cd..93a7071 100644 --- a/src/assembly/ed25519/blake2b.ts +++ b/src/assembly/ed25519/blake2b.ts @@ -1,112 +1,1082 @@ //! SPDX-FileCopyrightText: 2026 Chris Duncan //! SPDX-License-Identifier: GPL-3.0-or-later -const blake2b_state = StaticArray.fromArray([ - StaticArray.fromArray([0, 4, 8, 12]), - StaticArray.fromArray([1, 5, 9, 13]), - StaticArray.fromArray([2, 6, 10, 14]), - StaticArray.fromArray([3, 7, 11, 15]), - StaticArray.fromArray([0, 5, 10, 15]), - StaticArray.fromArray([1, 6, 11, 12]), - StaticArray.fromArray([2, 7, 8, 13]), - StaticArray.fromArray([3, 4, 9, 14]) -]) - -const blake2b_sigma = StaticArray.fromArray([ - StaticArray.fromArray([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]), - StaticArray.fromArray([14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3]), - StaticArray.fromArray([11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4]), - StaticArray.fromArray([7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8]), - StaticArray.fromArray([9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13]), - StaticArray.fromArray([2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9]), - StaticArray.fromArray([12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11]), - StaticArray.fromArray([13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10]), - StaticArray.fromArray([6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5]), - StaticArray.fromArray([10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0]), - StaticArray.fromArray([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]), - StaticArray.fromArray([14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3]) -]) - -// Initialization vector defined by BLAKE2. -const blake2b_iv = StaticArray.fromArray([ - 0x6a09e667f3bcc908, - 0xbb67ae8584caa73b, - 0x3c6ef372fe94f82b, - 0xa54ff53a5f1d36f1, - 0x510e527fade682d1, - 0x9b05688c2b3e6c1f, - 0x1f83d9abfb41bd6b, - 0x5be0cd19137e2179 -]) - export class Blake2b { - /** - * Buffers and counters - * - b: input buffer, filled and refilled by input data in 128 byte increments - * - c: buffer counter, points to current index in `b` - * - h: chain buffer, updated after each compression - * - m: message buffer, represents current state of `b` during compression - * - p: set according to BLAKE2 layout from constructor arguments - * - t: total byte counter, incremented by `c` (BLAKE2b supports 2¹²⁸-1) - * - v: state vector, set to bytes of `h` and `iv` then modified by `t` during compression - */ - b: StaticArray = new StaticArray(128) - c: u8 = 0 - h: StaticArray = new StaticArray(8) - m: StaticArray = new StaticArray(16) - p: StaticArray = new StaticArray(64) - t: v128 = v128.splat(0) - v: StaticArray = new StaticArray(16) - - @inline - clear (): void { + + /** + * Buffers and counters + * - b: input buffer, filled and refilled by input data in 128 byte increments + * - c: buffer counter, points to current index in `b` + * - h: chain buffer, updated after each compression + * - p: set according to BLAKE2 layout from constructor arguments + * - t: total byte counter, incremented by `c` + * - Note: BLAKE2b reference supports 2¹²⁸-1, but this implementation restricts to 2⁶⁴-1 for compatibility with WASM without SIMD) + */ + b: StaticArray = new StaticArray(128) + c: i32 = 0 + h: StaticArray = new StaticArray(8) + p: StaticArray = new StaticArray(64) + t: u64 = 0 + + + // COMPRESS + // Defined in BLAKE2 section 2.4 + compress (isFinal: u64): void { + // initialize compression buffers + // - m: message buffer, represents current state of `b` during compression + // - v: state vector, set to bytes of `h` and `iv` then modified by `t` during compression + + let v0: u64 = this.h[0] + let v8: u64 = 0x6a09e667f3bcc908 + + let v1: u64 = this.h[1] + let v9: u64 = 0xbb67ae8584caa73b + + let v2: u64 = this.h[2] + let v10: u64 = 0x3c6ef372fe94f82b + + let v3: u64 = this.h[3] + let v11: u64 = 0xa54ff53a5f1d36f1 + + let v4: u64 = this.h[4] + let v12: u64 = 0x510e527fade682d1 + + let v5: u64 = this.h[5] + let v13: u64 = 0x9b05688c2b3e6c1f + + let v6: u64 = this.h[6] + let v14: u64 = 0x1f83d9abfb41bd6b + + let v7: u64 = this.h[7] + let v15: u64 = 0x5be0cd19137e2179 + + // lo 64 bits of counter + v12 ^= this.t + + // hi 64 bits of counter, always 0 in this implementation + v13 ^= 0 + + // flip bits if "last block" flag is set so that v14 = ~v14 + v14 ^= isFinal + + // copy input buffer to message block + const b_ptr = changetype(this.b) + + let m0: u64 = load(b_ptr + 0) + + let m1: u64 = load(b_ptr + 8) + + let m2: u64 = load(b_ptr + 16) + + let m3: u64 = load(b_ptr + 24) + + let m4: u64 = load(b_ptr + 32) + + let m5: u64 = load(b_ptr + 40) + + let m6: u64 = load(b_ptr + 48) + + let m7: u64 = load(b_ptr + 56) + + let m8: u64 = load(b_ptr + 64) + + let m9: u64 = load(b_ptr + 72) + + let m10: u64 = load(b_ptr + 80) + + let m11: u64 = load(b_ptr + 88) + + let m12: u64 = load(b_ptr + 96) + + let m13: u64 = load(b_ptr + 104) + + let m14: u64 = load(b_ptr + 112) + + let m15: u64 = load(b_ptr + 120) + + // twelve rounds of mixing + + // ROUND 0 + + v0 = v0 + v4 + m0 + v12 = rotr(v12 ^ v0, 32) + v8 = v8 + v12 + v4 = rotr(v4 ^ v8, 24) + v0 = v0 + v4 + m1 + v12 = rotr(v12 ^ v0, 16) + v8 = v8 + v12 + v4 = rotr(v4 ^ v8, 63) + + v1 = v1 + v5 + m2 + v13 = rotr(v13 ^ v1, 32) + v9 = v9 + v13 + v5 = rotr(v5 ^ v9, 24) + v1 = v1 + v5 + m3 + v13 = rotr(v13 ^ v1, 16) + v9 = v9 + v13 + v5 = rotr(v5 ^ v9, 63) + + v2 = v2 + v6 + m4 + v14 = rotr(v14 ^ v2, 32) + v10 = v10 + v14 + v6 = rotr(v6 ^ v10, 24) + v2 = v2 + v6 + m5 + v14 = rotr(v14 ^ v2, 16) + v10 = v10 + v14 + v6 = rotr(v6 ^ v10, 63) + + v3 = v3 + v7 + m6 + v15 = rotr(v15 ^ v3, 32) + v11 = v11 + v15 + v7 = rotr(v7 ^ v11, 24) + v3 = v3 + v7 + m7 + v15 = rotr(v15 ^ v3, 16) + v11 = v11 + v15 + v7 = rotr(v7 ^ v11, 63) + + v0 = v0 + v5 + m8 + v15 = rotr(v15 ^ v0, 32) + v10 = v10 + v15 + v5 = rotr(v5 ^ v10, 24) + v0 = v0 + v5 + m9 + v15 = rotr(v15 ^ v0, 16) + v10 = v10 + v15 + v5 = rotr(v5 ^ v10, 63) + + v1 = v1 + v6 + m10 + v12 = rotr(v12 ^ v1, 32) + v11 = v11 + v12 + v6 = rotr(v6 ^ v11, 24) + v1 = v1 + v6 + m11 + v12 = rotr(v12 ^ v1, 16) + v11 = v11 + v12 + v6 = rotr(v6 ^ v11, 63) + + v2 = v2 + v7 + m12 + v13 = rotr(v13 ^ v2, 32) + v8 = v8 + v13 + v7 = rotr(v7 ^ v8, 24) + v2 = v2 + v7 + m13 + v13 = rotr(v13 ^ v2, 16) + v8 = v8 + v13 + v7 = rotr(v7 ^ v8, 63) + + v3 = v3 + v4 + m14 + v14 = rotr(v14 ^ v3, 32) + v9 = v9 + v14 + v4 = rotr(v4 ^ v9, 24) + v3 = v3 + v4 + m15 + v14 = rotr(v14 ^ v3, 16) + v9 = v9 + v14 + v4 = rotr(v4 ^ v9, 63) + + // ROUND 1 + + v0 = v0 + v4 + m14 + v12 = rotr(v12 ^ v0, 32) + v8 = v8 + v12 + v4 = rotr(v4 ^ v8, 24) + v0 = v0 + v4 + m10 + v12 = rotr(v12 ^ v0, 16) + v8 = v8 + v12 + v4 = rotr(v4 ^ v8, 63) + + v1 = v1 + v5 + m4 + v13 = rotr(v13 ^ v1, 32) + v9 = v9 + v13 + v5 = rotr(v5 ^ v9, 24) + v1 = v1 + v5 + m8 + v13 = rotr(v13 ^ v1, 16) + v9 = v9 + v13 + v5 = rotr(v5 ^ v9, 63) + + v2 = v2 + v6 + m9 + v14 = rotr(v14 ^ v2, 32) + v10 = v10 + v14 + v6 = rotr(v6 ^ v10, 24) + v2 = v2 + v6 + m15 + v14 = rotr(v14 ^ v2, 16) + v10 = v10 + v14 + v6 = rotr(v6 ^ v10, 63) + + v3 = v3 + v7 + m13 + v15 = rotr(v15 ^ v3, 32) + v11 = v11 + v15 + v7 = rotr(v7 ^ v11, 24) + v3 = v3 + v7 + m6 + v15 = rotr(v15 ^ v3, 16) + v11 = v11 + v15 + v7 = rotr(v7 ^ v11, 63) + + v0 = v0 + v5 + m1 + v15 = rotr(v15 ^ v0, 32) + v10 = v10 + v15 + v5 = rotr(v5 ^ v10, 24) + v0 = v0 + v5 + m12 + v15 = rotr(v15 ^ v0, 16) + v10 = v10 + v15 + v5 = rotr(v5 ^ v10, 63) + + v1 = v1 + v6 + m0 + v12 = rotr(v12 ^ v1, 32) + v11 = v11 + v12 + v6 = rotr(v6 ^ v11, 24) + v1 = v1 + v6 + m2 + v12 = rotr(v12 ^ v1, 16) + v11 = v11 + v12 + v6 = rotr(v6 ^ v11, 63) + + v2 = v2 + v7 + m11 + v13 = rotr(v13 ^ v2, 32) + v8 = v8 + v13 + v7 = rotr(v7 ^ v8, 24) + v2 = v2 + v7 + m7 + v13 = rotr(v13 ^ v2, 16) + v8 = v8 + v13 + v7 = rotr(v7 ^ v8, 63) + + v3 = v3 + v4 + m5 + v14 = rotr(v14 ^ v3, 32) + v9 = v9 + v14 + v4 = rotr(v4 ^ v9, 24) + v3 = v3 + v4 + m3 + v14 = rotr(v14 ^ v3, 16) + v9 = v9 + v14 + v4 = rotr(v4 ^ v9, 63) + + // ROUND 2 + + v0 = v0 + v4 + m11 + v12 = rotr(v12 ^ v0, 32) + v8 = v8 + v12 + v4 = rotr(v4 ^ v8, 24) + v0 = v0 + v4 + m8 + v12 = rotr(v12 ^ v0, 16) + v8 = v8 + v12 + v4 = rotr(v4 ^ v8, 63) + + v1 = v1 + v5 + m12 + v13 = rotr(v13 ^ v1, 32) + v9 = v9 + v13 + v5 = rotr(v5 ^ v9, 24) + v1 = v1 + v5 + m0 + v13 = rotr(v13 ^ v1, 16) + v9 = v9 + v13 + v5 = rotr(v5 ^ v9, 63) + + v2 = v2 + v6 + m5 + v14 = rotr(v14 ^ v2, 32) + v10 = v10 + v14 + v6 = rotr(v6 ^ v10, 24) + v2 = v2 + v6 + m2 + v14 = rotr(v14 ^ v2, 16) + v10 = v10 + v14 + v6 = rotr(v6 ^ v10, 63) + + v3 = v3 + v7 + m15 + v15 = rotr(v15 ^ v3, 32) + v11 = v11 + v15 + v7 = rotr(v7 ^ v11, 24) + v3 = v3 + v7 + m13 + v15 = rotr(v15 ^ v3, 16) + v11 = v11 + v15 + v7 = rotr(v7 ^ v11, 63) + + v0 = v0 + v5 + m10 + v15 = rotr(v15 ^ v0, 32) + v10 = v10 + v15 + v5 = rotr(v5 ^ v10, 24) + v0 = v0 + v5 + m14 + v15 = rotr(v15 ^ v0, 16) + v10 = v10 + v15 + v5 = rotr(v5 ^ v10, 63) + + v1 = v1 + v6 + m3 + v12 = rotr(v12 ^ v1, 32) + v11 = v11 + v12 + v6 = rotr(v6 ^ v11, 24) + v1 = v1 + v6 + m6 + v12 = rotr(v12 ^ v1, 16) + v11 = v11 + v12 + v6 = rotr(v6 ^ v11, 63) + + v2 = v2 + v7 + m7 + v13 = rotr(v13 ^ v2, 32) + v8 = v8 + v13 + v7 = rotr(v7 ^ v8, 24) + v2 = v2 + v7 + m1 + v13 = rotr(v13 ^ v2, 16) + v8 = v8 + v13 + v7 = rotr(v7 ^ v8, 63) + + v3 = v3 + v4 + m9 + v14 = rotr(v14 ^ v3, 32) + v9 = v9 + v14 + v4 = rotr(v4 ^ v9, 24) + v3 = v3 + v4 + m4 + v14 = rotr(v14 ^ v3, 16) + v9 = v9 + v14 + v4 = rotr(v4 ^ v9, 63) + + // ROUND 3 + + v0 = v0 + v4 + m7 + v12 = rotr(v12 ^ v0, 32) + v8 = v8 + v12 + v4 = rotr(v4 ^ v8, 24) + v0 = v0 + v4 + m9 + v12 = rotr(v12 ^ v0, 16) + v8 = v8 + v12 + v4 = rotr(v4 ^ v8, 63) + + v1 = v1 + v5 + m3 + v13 = rotr(v13 ^ v1, 32) + v9 = v9 + v13 + v5 = rotr(v5 ^ v9, 24) + v1 = v1 + v5 + m1 + v13 = rotr(v13 ^ v1, 16) + v9 = v9 + v13 + v5 = rotr(v5 ^ v9, 63) + + v2 = v2 + v6 + m13 + v14 = rotr(v14 ^ v2, 32) + v10 = v10 + v14 + v6 = rotr(v6 ^ v10, 24) + v2 = v2 + v6 + m12 + v14 = rotr(v14 ^ v2, 16) + v10 = v10 + v14 + v6 = rotr(v6 ^ v10, 63) + + v3 = v3 + v7 + m11 + v15 = rotr(v15 ^ v3, 32) + v11 = v11 + v15 + v7 = rotr(v7 ^ v11, 24) + v3 = v3 + v7 + m14 + v15 = rotr(v15 ^ v3, 16) + v11 = v11 + v15 + v7 = rotr(v7 ^ v11, 63) + + v0 = v0 + v5 + m2 + v15 = rotr(v15 ^ v0, 32) + v10 = v10 + v15 + v5 = rotr(v5 ^ v10, 24) + v0 = v0 + v5 + m6 + v15 = rotr(v15 ^ v0, 16) + v10 = v10 + v15 + v5 = rotr(v5 ^ v10, 63) + + v1 = v1 + v6 + m5 + v12 = rotr(v12 ^ v1, 32) + v11 = v11 + v12 + v6 = rotr(v6 ^ v11, 24) + v1 = v1 + v6 + m10 + v12 = rotr(v12 ^ v1, 16) + v11 = v11 + v12 + v6 = rotr(v6 ^ v11, 63) + + v2 = v2 + v7 + m4 + v13 = rotr(v13 ^ v2, 32) + v8 = v8 + v13 + v7 = rotr(v7 ^ v8, 24) + v2 = v2 + v7 + m0 + v13 = rotr(v13 ^ v2, 16) + v8 = v8 + v13 + v7 = rotr(v7 ^ v8, 63) + + v3 = v3 + v4 + m15 + v14 = rotr(v14 ^ v3, 32) + v9 = v9 + v14 + v4 = rotr(v4 ^ v9, 24) + v3 = v3 + v4 + m8 + v14 = rotr(v14 ^ v3, 16) + v9 = v9 + v14 + v4 = rotr(v4 ^ v9, 63) + + // ROUND 4 + + v0 = v0 + v4 + m9 + v12 = rotr(v12 ^ v0, 32) + v8 = v8 + v12 + v4 = rotr(v4 ^ v8, 24) + v0 = v0 + v4 + m0 + v12 = rotr(v12 ^ v0, 16) + v8 = v8 + v12 + v4 = rotr(v4 ^ v8, 63) + + v1 = v1 + v5 + m5 + v13 = rotr(v13 ^ v1, 32) + v9 = v9 + v13 + v5 = rotr(v5 ^ v9, 24) + v1 = v1 + v5 + m7 + v13 = rotr(v13 ^ v1, 16) + v9 = v9 + v13 + v5 = rotr(v5 ^ v9, 63) + + v2 = v2 + v6 + m2 + v14 = rotr(v14 ^ v2, 32) + v10 = v10 + v14 + v6 = rotr(v6 ^ v10, 24) + v2 = v2 + v6 + m4 + v14 = rotr(v14 ^ v2, 16) + v10 = v10 + v14 + v6 = rotr(v6 ^ v10, 63) + + v3 = v3 + v7 + m10 + v15 = rotr(v15 ^ v3, 32) + v11 = v11 + v15 + v7 = rotr(v7 ^ v11, 24) + v3 = v3 + v7 + m15 + v15 = rotr(v15 ^ v3, 16) + v11 = v11 + v15 + v7 = rotr(v7 ^ v11, 63) + + v0 = v0 + v5 + m14 + v15 = rotr(v15 ^ v0, 32) + v10 = v10 + v15 + v5 = rotr(v5 ^ v10, 24) + v0 = v0 + v5 + m1 + v15 = rotr(v15 ^ v0, 16) + v10 = v10 + v15 + v5 = rotr(v5 ^ v10, 63) + + v1 = v1 + v6 + m11 + v12 = rotr(v12 ^ v1, 32) + v11 = v11 + v12 + v6 = rotr(v6 ^ v11, 24) + v1 = v1 + v6 + m12 + v12 = rotr(v12 ^ v1, 16) + v11 = v11 + v12 + v6 = rotr(v6 ^ v11, 63) + + v2 = v2 + v7 + m6 + v13 = rotr(v13 ^ v2, 32) + v8 = v8 + v13 + v7 = rotr(v7 ^ v8, 24) + v2 = v2 + v7 + m8 + v13 = rotr(v13 ^ v2, 16) + v8 = v8 + v13 + v7 = rotr(v7 ^ v8, 63) + + v3 = v3 + v4 + m3 + v14 = rotr(v14 ^ v3, 32) + v9 = v9 + v14 + v4 = rotr(v4 ^ v9, 24) + v3 = v3 + v4 + m13 + v14 = rotr(v14 ^ v3, 16) + v9 = v9 + v14 + v4 = rotr(v4 ^ v9, 63) + + // ROUND 5 + + v0 = v0 + v4 + m2 + v12 = rotr(v12 ^ v0, 32) + v8 = v8 + v12 + v4 = rotr(v4 ^ v8, 24) + v0 = v0 + v4 + m12 + v12 = rotr(v12 ^ v0, 16) + v8 = v8 + v12 + v4 = rotr(v4 ^ v8, 63) + + v1 = v1 + v5 + m6 + v13 = rotr(v13 ^ v1, 32) + v9 = v9 + v13 + v5 = rotr(v5 ^ v9, 24) + v1 = v1 + v5 + m10 + v13 = rotr(v13 ^ v1, 16) + v9 = v9 + v13 + v5 = rotr(v5 ^ v9, 63) + + v2 = v2 + v6 + m0 + v14 = rotr(v14 ^ v2, 32) + v10 = v10 + v14 + v6 = rotr(v6 ^ v10, 24) + v2 = v2 + v6 + m11 + v14 = rotr(v14 ^ v2, 16) + v10 = v10 + v14 + v6 = rotr(v6 ^ v10, 63) + + v3 = v3 + v7 + m8 + v15 = rotr(v15 ^ v3, 32) + v11 = v11 + v15 + v7 = rotr(v7 ^ v11, 24) + v3 = v3 + v7 + m3 + v15 = rotr(v15 ^ v3, 16) + v11 = v11 + v15 + v7 = rotr(v7 ^ v11, 63) + + v0 = v0 + v5 + m4 + v15 = rotr(v15 ^ v0, 32) + v10 = v10 + v15 + v5 = rotr(v5 ^ v10, 24) + v0 = v0 + v5 + m13 + v15 = rotr(v15 ^ v0, 16) + v10 = v10 + v15 + v5 = rotr(v5 ^ v10, 63) + + v1 = v1 + v6 + m7 + v12 = rotr(v12 ^ v1, 32) + v11 = v11 + v12 + v6 = rotr(v6 ^ v11, 24) + v1 = v1 + v6 + m5 + v12 = rotr(v12 ^ v1, 16) + v11 = v11 + v12 + v6 = rotr(v6 ^ v11, 63) + + v2 = v2 + v7 + m15 + v13 = rotr(v13 ^ v2, 32) + v8 = v8 + v13 + v7 = rotr(v7 ^ v8, 24) + v2 = v2 + v7 + m14 + v13 = rotr(v13 ^ v2, 16) + v8 = v8 + v13 + v7 = rotr(v7 ^ v8, 63) + + v3 = v3 + v4 + m1 + v14 = rotr(v14 ^ v3, 32) + v9 = v9 + v14 + v4 = rotr(v4 ^ v9, 24) + v3 = v3 + v4 + m9 + v14 = rotr(v14 ^ v3, 16) + v9 = v9 + v14 + v4 = rotr(v4 ^ v9, 63) + + // ROUND 6 + + v0 = v0 + v4 + m12 + v12 = rotr(v12 ^ v0, 32) + v8 = v8 + v12 + v4 = rotr(v4 ^ v8, 24) + v0 = v0 + v4 + m5 + v12 = rotr(v12 ^ v0, 16) + v8 = v8 + v12 + v4 = rotr(v4 ^ v8, 63) + + v1 = v1 + v5 + m1 + v13 = rotr(v13 ^ v1, 32) + v9 = v9 + v13 + v5 = rotr(v5 ^ v9, 24) + v1 = v1 + v5 + m15 + v13 = rotr(v13 ^ v1, 16) + v9 = v9 + v13 + v5 = rotr(v5 ^ v9, 63) + + v2 = v2 + v6 + m14 + v14 = rotr(v14 ^ v2, 32) + v10 = v10 + v14 + v6 = rotr(v6 ^ v10, 24) + v2 = v2 + v6 + m13 + v14 = rotr(v14 ^ v2, 16) + v10 = v10 + v14 + v6 = rotr(v6 ^ v10, 63) + + v3 = v3 + v7 + m4 + v15 = rotr(v15 ^ v3, 32) + v11 = v11 + v15 + v7 = rotr(v7 ^ v11, 24) + v3 = v3 + v7 + m10 + v15 = rotr(v15 ^ v3, 16) + v11 = v11 + v15 + v7 = rotr(v7 ^ v11, 63) + + v0 = v0 + v5 + m0 + v15 = rotr(v15 ^ v0, 32) + v10 = v10 + v15 + v5 = rotr(v5 ^ v10, 24) + v0 = v0 + v5 + m7 + v15 = rotr(v15 ^ v0, 16) + v10 = v10 + v15 + v5 = rotr(v5 ^ v10, 63) + + v1 = v1 + v6 + m6 + v12 = rotr(v12 ^ v1, 32) + v11 = v11 + v12 + v6 = rotr(v6 ^ v11, 24) + v1 = v1 + v6 + m3 + v12 = rotr(v12 ^ v1, 16) + v11 = v11 + v12 + v6 = rotr(v6 ^ v11, 63) + + v2 = v2 + v7 + m9 + v13 = rotr(v13 ^ v2, 32) + v8 = v8 + v13 + v7 = rotr(v7 ^ v8, 24) + v2 = v2 + v7 + m2 + v13 = rotr(v13 ^ v2, 16) + v8 = v8 + v13 + v7 = rotr(v7 ^ v8, 63) + + v3 = v3 + v4 + m8 + v14 = rotr(v14 ^ v3, 32) + v9 = v9 + v14 + v4 = rotr(v4 ^ v9, 24) + v3 = v3 + v4 + m11 + v14 = rotr(v14 ^ v3, 16) + v9 = v9 + v14 + v4 = rotr(v4 ^ v9, 63) + + // ROUND 7 + + v0 = v0 + v4 + m13 + v12 = rotr(v12 ^ v0, 32) + v8 = v8 + v12 + v4 = rotr(v4 ^ v8, 24) + v0 = v0 + v4 + m11 + v12 = rotr(v12 ^ v0, 16) + v8 = v8 + v12 + v4 = rotr(v4 ^ v8, 63) + + v1 = v1 + v5 + m7 + v13 = rotr(v13 ^ v1, 32) + v9 = v9 + v13 + v5 = rotr(v5 ^ v9, 24) + v1 = v1 + v5 + m14 + v13 = rotr(v13 ^ v1, 16) + v9 = v9 + v13 + v5 = rotr(v5 ^ v9, 63) + + v2 = v2 + v6 + m12 + v14 = rotr(v14 ^ v2, 32) + v10 = v10 + v14 + v6 = rotr(v6 ^ v10, 24) + v2 = v2 + v6 + m1 + v14 = rotr(v14 ^ v2, 16) + v10 = v10 + v14 + v6 = rotr(v6 ^ v10, 63) + + v3 = v3 + v7 + m3 + v15 = rotr(v15 ^ v3, 32) + v11 = v11 + v15 + v7 = rotr(v7 ^ v11, 24) + v3 = v3 + v7 + m9 + v15 = rotr(v15 ^ v3, 16) + v11 = v11 + v15 + v7 = rotr(v7 ^ v11, 63) + + v0 = v0 + v5 + m5 + v15 = rotr(v15 ^ v0, 32) + v10 = v10 + v15 + v5 = rotr(v5 ^ v10, 24) + v0 = v0 + v5 + m0 + v15 = rotr(v15 ^ v0, 16) + v10 = v10 + v15 + v5 = rotr(v5 ^ v10, 63) + + v1 = v1 + v6 + m15 + v12 = rotr(v12 ^ v1, 32) + v11 = v11 + v12 + v6 = rotr(v6 ^ v11, 24) + v1 = v1 + v6 + m4 + v12 = rotr(v12 ^ v1, 16) + v11 = v11 + v12 + v6 = rotr(v6 ^ v11, 63) + + v2 = v2 + v7 + m8 + v13 = rotr(v13 ^ v2, 32) + v8 = v8 + v13 + v7 = rotr(v7 ^ v8, 24) + v2 = v2 + v7 + m6 + v13 = rotr(v13 ^ v2, 16) + v8 = v8 + v13 + v7 = rotr(v7 ^ v8, 63) + + v3 = v3 + v4 + m2 + v14 = rotr(v14 ^ v3, 32) + v9 = v9 + v14 + v4 = rotr(v4 ^ v9, 24) + v3 = v3 + v4 + m10 + v14 = rotr(v14 ^ v3, 16) + v9 = v9 + v14 + v4 = rotr(v4 ^ v9, 63) + + // ROUND 8 + + v0 = v0 + v4 + m6 + v12 = rotr(v12 ^ v0, 32) + v8 = v8 + v12 + v4 = rotr(v4 ^ v8, 24) + v0 = v0 + v4 + m15 + v12 = rotr(v12 ^ v0, 16) + v8 = v8 + v12 + v4 = rotr(v4 ^ v8, 63) + + v1 = v1 + v5 + m14 + v13 = rotr(v13 ^ v1, 32) + v9 = v9 + v13 + v5 = rotr(v5 ^ v9, 24) + v1 = v1 + v5 + m9 + v13 = rotr(v13 ^ v1, 16) + v9 = v9 + v13 + v5 = rotr(v5 ^ v9, 63) + + v2 = v2 + v6 + m11 + v14 = rotr(v14 ^ v2, 32) + v10 = v10 + v14 + v6 = rotr(v6 ^ v10, 24) + v2 = v2 + v6 + m3 + v14 = rotr(v14 ^ v2, 16) + v10 = v10 + v14 + v6 = rotr(v6 ^ v10, 63) + + v3 = v3 + v7 + m0 + v15 = rotr(v15 ^ v3, 32) + v11 = v11 + v15 + v7 = rotr(v7 ^ v11, 24) + v3 = v3 + v7 + m8 + v15 = rotr(v15 ^ v3, 16) + v11 = v11 + v15 + v7 = rotr(v7 ^ v11, 63) + + v0 = v0 + v5 + m12 + v15 = rotr(v15 ^ v0, 32) + v10 = v10 + v15 + v5 = rotr(v5 ^ v10, 24) + v0 = v0 + v5 + m2 + v15 = rotr(v15 ^ v0, 16) + v10 = v10 + v15 + v5 = rotr(v5 ^ v10, 63) + + v1 = v1 + v6 + m13 + v12 = rotr(v12 ^ v1, 32) + v11 = v11 + v12 + v6 = rotr(v6 ^ v11, 24) + v1 = v1 + v6 + m7 + v12 = rotr(v12 ^ v1, 16) + v11 = v11 + v12 + v6 = rotr(v6 ^ v11, 63) + + v2 = v2 + v7 + m1 + v13 = rotr(v13 ^ v2, 32) + v8 = v8 + v13 + v7 = rotr(v7 ^ v8, 24) + v2 = v2 + v7 + m4 + v13 = rotr(v13 ^ v2, 16) + v8 = v8 + v13 + v7 = rotr(v7 ^ v8, 63) + + v3 = v3 + v4 + m10 + v14 = rotr(v14 ^ v3, 32) + v9 = v9 + v14 + v4 = rotr(v4 ^ v9, 24) + v3 = v3 + v4 + m5 + v14 = rotr(v14 ^ v3, 16) + v9 = v9 + v14 + v4 = rotr(v4 ^ v9, 63) + + // ROUND 9 + + v0 = v0 + v4 + m10 + v12 = rotr(v12 ^ v0, 32) + v8 = v8 + v12 + v4 = rotr(v4 ^ v8, 24) + v0 = v0 + v4 + m2 + v12 = rotr(v12 ^ v0, 16) + v8 = v8 + v12 + v4 = rotr(v4 ^ v8, 63) + + v1 = v1 + v5 + m8 + v13 = rotr(v13 ^ v1, 32) + v9 = v9 + v13 + v5 = rotr(v5 ^ v9, 24) + v1 = v1 + v5 + m4 + v13 = rotr(v13 ^ v1, 16) + v9 = v9 + v13 + v5 = rotr(v5 ^ v9, 63) + + v2 = v2 + v6 + m7 + v14 = rotr(v14 ^ v2, 32) + v10 = v10 + v14 + v6 = rotr(v6 ^ v10, 24) + v2 = v2 + v6 + m6 + v14 = rotr(v14 ^ v2, 16) + v10 = v10 + v14 + v6 = rotr(v6 ^ v10, 63) + + v3 = v3 + v7 + m1 + v15 = rotr(v15 ^ v3, 32) + v11 = v11 + v15 + v7 = rotr(v7 ^ v11, 24) + v3 = v3 + v7 + m5 + v15 = rotr(v15 ^ v3, 16) + v11 = v11 + v15 + v7 = rotr(v7 ^ v11, 63) + + v0 = v0 + v5 + m15 + v15 = rotr(v15 ^ v0, 32) + v10 = v10 + v15 + v5 = rotr(v5 ^ v10, 24) + v0 = v0 + v5 + m11 + v15 = rotr(v15 ^ v0, 16) + v10 = v10 + v15 + v5 = rotr(v5 ^ v10, 63) + + v1 = v1 + v6 + m9 + v12 = rotr(v12 ^ v1, 32) + v11 = v11 + v12 + v6 = rotr(v6 ^ v11, 24) + v1 = v1 + v6 + m14 + v12 = rotr(v12 ^ v1, 16) + v11 = v11 + v12 + v6 = rotr(v6 ^ v11, 63) + + v2 = v2 + v7 + m3 + v13 = rotr(v13 ^ v2, 32) + v8 = v8 + v13 + v7 = rotr(v7 ^ v8, 24) + v2 = v2 + v7 + m12 + v13 = rotr(v13 ^ v2, 16) + v8 = v8 + v13 + v7 = rotr(v7 ^ v8, 63) + + v3 = v3 + v4 + m13 + v14 = rotr(v14 ^ v3, 32) + v9 = v9 + v14 + v4 = rotr(v4 ^ v9, 24) + v3 = v3 + v4 + m0 + v14 = rotr(v14 ^ v3, 16) + v9 = v9 + v14 + v4 = rotr(v4 ^ v9, 63) + + // ROUND 10 + + v0 = v0 + v4 + m0 + v12 = rotr(v12 ^ v0, 32) + v8 = v8 + v12 + v4 = rotr(v4 ^ v8, 24) + v0 = v0 + v4 + m1 + v12 = rotr(v12 ^ v0, 16) + v8 = v8 + v12 + v4 = rotr(v4 ^ v8, 63) + + v1 = v1 + v5 + m2 + v13 = rotr(v13 ^ v1, 32) + v9 = v9 + v13 + v5 = rotr(v5 ^ v9, 24) + v1 = v1 + v5 + m3 + v13 = rotr(v13 ^ v1, 16) + v9 = v9 + v13 + v5 = rotr(v5 ^ v9, 63) + + v2 = v2 + v6 + m4 + v14 = rotr(v14 ^ v2, 32) + v10 = v10 + v14 + v6 = rotr(v6 ^ v10, 24) + v2 = v2 + v6 + m5 + v14 = rotr(v14 ^ v2, 16) + v10 = v10 + v14 + v6 = rotr(v6 ^ v10, 63) + + v3 = v3 + v7 + m6 + v15 = rotr(v15 ^ v3, 32) + v11 = v11 + v15 + v7 = rotr(v7 ^ v11, 24) + v3 = v3 + v7 + m7 + v15 = rotr(v15 ^ v3, 16) + v11 = v11 + v15 + v7 = rotr(v7 ^ v11, 63) + + v0 = v0 + v5 + m8 + v15 = rotr(v15 ^ v0, 32) + v10 = v10 + v15 + v5 = rotr(v5 ^ v10, 24) + v0 = v0 + v5 + m9 + v15 = rotr(v15 ^ v0, 16) + v10 = v10 + v15 + v5 = rotr(v5 ^ v10, 63) + + v1 = v1 + v6 + m10 + v12 = rotr(v12 ^ v1, 32) + v11 = v11 + v12 + v6 = rotr(v6 ^ v11, 24) + v1 = v1 + v6 + m11 + v12 = rotr(v12 ^ v1, 16) + v11 = v11 + v12 + v6 = rotr(v6 ^ v11, 63) + + v2 = v2 + v7 + m12 + v13 = rotr(v13 ^ v2, 32) + v8 = v8 + v13 + v7 = rotr(v7 ^ v8, 24) + v2 = v2 + v7 + m13 + v13 = rotr(v13 ^ v2, 16) + v8 = v8 + v13 + v7 = rotr(v7 ^ v8, 63) + + v3 = v3 + v4 + m14 + v14 = rotr(v14 ^ v3, 32) + v9 = v9 + v14 + v4 = rotr(v4 ^ v9, 24) + v3 = v3 + v4 + m15 + v14 = rotr(v14 ^ v3, 16) + v9 = v9 + v14 + v4 = rotr(v4 ^ v9, 63) + + // ROUND 11 + + v0 = v0 + v4 + m14 + v12 = rotr(v12 ^ v0, 32) + v8 = v8 + v12 + v4 = rotr(v4 ^ v8, 24) + v0 = v0 + v4 + m10 + v12 = rotr(v12 ^ v0, 16) + v8 = v8 + v12 + v4 = rotr(v4 ^ v8, 63) + + v1 = v1 + v5 + m4 + v13 = rotr(v13 ^ v1, 32) + v9 = v9 + v13 + v5 = rotr(v5 ^ v9, 24) + v1 = v1 + v5 + m8 + v13 = rotr(v13 ^ v1, 16) + v9 = v9 + v13 + v5 = rotr(v5 ^ v9, 63) + + v2 = v2 + v6 + m9 + v14 = rotr(v14 ^ v2, 32) + v10 = v10 + v14 + v6 = rotr(v6 ^ v10, 24) + v2 = v2 + v6 + m15 + v14 = rotr(v14 ^ v2, 16) + v10 = v10 + v14 + v6 = rotr(v6 ^ v10, 63) + + v3 = v3 + v7 + m13 + v15 = rotr(v15 ^ v3, 32) + v11 = v11 + v15 + v7 = rotr(v7 ^ v11, 24) + v3 = v3 + v7 + m6 + v15 = rotr(v15 ^ v3, 16) + v11 = v11 + v15 + v7 = rotr(v7 ^ v11, 63) + + v0 = v0 + v5 + m1 + v15 = rotr(v15 ^ v0, 32) + v10 = v10 + v15 + v5 = rotr(v5 ^ v10, 24) + v0 = v0 + v5 + m12 + v15 = rotr(v15 ^ v0, 16) + v10 = v10 + v15 + v5 = rotr(v5 ^ v10, 63) + + v1 = v1 + v6 + m0 + v12 = rotr(v12 ^ v1, 32) + v11 = v11 + v12 + v6 = rotr(v6 ^ v11, 24) + v1 = v1 + v6 + m2 + v12 = rotr(v12 ^ v1, 16) + v11 = v11 + v12 + v6 = rotr(v6 ^ v11, 63) + + v2 = v2 + v7 + m11 + v13 = rotr(v13 ^ v2, 32) + v8 = v8 + v13 + v7 = rotr(v7 ^ v8, 24) + v2 = v2 + v7 + m7 + v13 = rotr(v13 ^ v2, 16) + v8 = v8 + v13 + v7 = rotr(v7 ^ v8, 63) + + v3 = v3 + v4 + m5 + v14 = rotr(v14 ^ v3, 32) + v9 = v9 + v14 + v4 = rotr(v4 ^ v9, 24) + v3 = v3 + v4 + m3 + v14 = rotr(v14 ^ v3, 16) + v9 = v9 + v14 + v4 = rotr(v4 ^ v9, 63) + + // set new chain value + + this.h[0] ^= v0 ^ v8 + + this.h[1] ^= v1 ^ v9 + + this.h[2] ^= v2 ^ v10 + + this.h[3] ^= v3 ^ v11 + + this.h[4] ^= v4 ^ v12 + + this.h[5] ^= v5 ^ v13 + + this.h[6] ^= v6 ^ v14 + + this.h[7] ^= v7 ^ v15 + + } + + + + + init(): Blake2b { + // reset buffers and counters + this.b.fill(0) this.c = 0 this.h.fill(0) - this.m.fill(0) this.p.fill(0) - this.t = v128.splat(0) - this.v.fill(0) - } - - init (): Blake2b { - // reset buffers and counters - this.clear() - - // initialize parameter block - this.p[0] = 64 // always 64 bytes for this implementation - this.p[1] = 0 // no key - this.p[2] = 1 // fanout - this.p[3] = 1 // depth + this.t = 0 + + // initialize parameter block + this.p[0] = 64 // always 64 bytes for this implementation + this.p[1] = 0 // no key + this.p[2] = 1 // fanout + this.p[3] = 1 // depth + // initialize hash state - memory.copy(changetype(this.h), changetype(blake2b_iv), 64) + + this.h[0] = 0x6a09e667f3bcc908 + + this.h[1] = 0xbb67ae8584caa73b + + this.h[2] = 0x3c6ef372fe94f82b + + this.h[3] = 0xa54ff53a5f1d36f1 + + this.h[4] = 0x510e527fade682d1 + + this.h[5] = 0x9b05688c2b3e6c1f + + this.h[6] = 0x1f83d9abfb41bd6b + + this.h[7] = 0x5be0cd19137e2179 + for (let i = 0; i < 8; i++) { this.h[i] ^= load(changetype(this.p) + (i << 3)) } return this } + // input: variable-length message bytes passed by user to be hashed update (input: StaticArray, length: i32): Blake2b { - for (let i = 0; i < length; i++) { + const input_ptr: usize = changetype(input) + const b_ptr: usize = changetype(this.b) + + let i = 0; + while (i < length) { // is buffer full? - if (this.c === this.b.length) { + if (this.c === 128) { // increment total byte counter - this.t = v128.add(this.t, i64x2(this.b.length, u64(v128.extract_lane(this.t, 0) + u64(this.b.length) < u64(this.b.length)))) + this.t += 128 // reset buffer counter to zero this.c = 0 // compress (not final) - this.COMPRESS(false) - } + this.compress(0) - // increment buffer counter and set byte - this.b[this.c++] = input[i] + } else { + const b_rem = 128 - this.c + const l_rem = length - i + const n = b_rem < l_rem ? b_rem : l_rem + memory.copy(b_ptr + this.c, input_ptr + i, n) + this.c += n + i += n + } } return this } @@ -115,73 +1085,24 @@ export class Blake2b { digest (output: StaticArray): void { // add final message block size to total bytes - this.t = v128.add(this.t, i64x2(this.c, u64(v128.extract_lane(this.t, 0) + u64(this.c) < u64(this.c)))) + this.t += this.c // pad final block with zeros this.b.fill(0, this.c) // set final block flag and compress - this.COMPRESS(true) + this.compress(u64(0 - 1)) // return byte array of 64-byte chain buffer memory.copy(changetype(output), changetype(this.h), 64) // clear internal buffers and counters - this.clear() - } - - // Defined in BLAKE2 section 2.4 - @inline - COMPRESS (isFinal: bool): void { - - // initialize state vector - memory.copy(changetype(this.v), changetype(this.h), 64) - memory.copy(changetype(this.v) + 64, changetype(blake2b_iv), 64) - - // lo 64 bits of counter - this.v[12] ^= v128.extract_lane(this.t, 0) - - // hi 64 bits of counter - this.v[13] ^= v128.extract_lane(this.t, 1) - - // flip bits if "last block" flag is set so that v[14] = ~v[14] - this.v[14] ^= 0 - u64(isFinal) - - // copy input buffer to message block - memory.copy(changetype(this.m), changetype(this.b), 128) - - // twelve rounds of mixing - for (let r: u8 = 0; r < 12; r++) { - this.ROUND(r) - } - - // set new chain value - for (let i = 0; i < 8; i++) { - this.h[i] ^= this.v[i] ^ this.v[i + 8] - } - } - - @inline - ROUND (r: u8): void { - for (let i = 0; i < 8; i++) { - const a = blake2b_state[i][0] - const b = blake2b_state[i][1] - const c = blake2b_state[i][2] - const d = blake2b_state[i][3] - const s = blake2b_sigma[r] - this.G(a, b, c, d, s[i << 1], s[(i << 1) + 1]) - } - } - - @inline - G (a: u8, b: u8, c: u8, d: u8, x: u8, y: u8): void { - this.v[a] = this.v[a] + this.v[b] + this.m[x] - this.v[d] = rotr(this.v[d] ^ this.v[a], 32) - this.v[c] = this.v[c] + this.v[d] - this.v[b] = rotr(this.v[b] ^ this.v[c], 24) - this.v[a] = this.v[a] + this.v[b] + this.m[y] - this.v[d] = rotr(this.v[d] ^ this.v[a], 16) - this.v[c] = this.v[c] + this.v[d] - this.v[b] = rotr(this.v[b] ^ this.v[c], 63) + + this.b.fill(0) + this.c = 0 + this.h.fill(0) + this.p.fill(0) + this.t = 0 + } } diff --git a/src/assembly/ed25519/crypto_sign.ts b/src/assembly/ed25519/crypto_sign.ts deleted file mode 100644 index 7e0eb09..0000000 --- a/src/assembly/ed25519/crypto_sign.ts +++ /dev/null @@ -1,92 +0,0 @@ -//! SPDX-FileCopyrightText: 2026 Chris Duncan -//! SPDX-License-Identifier: GPL-3.0-or-later - -import { KEY_BYTELENGTH } from '../constants' -import { crypto_derive } from '../crypto_derive' -import { Blake2b } from './blake2b' -import { ge_scalarmult_base_tobytes } from './ge' -import { sc_muladd, sc_reduce } from './sc' -import { clamp } from './utils' - -// crypto_hash function -const blake2b = new Blake2b() - -// algorithm variables -const A = new StaticArray(32) -const h = new StaticArray(64) -const s = new StaticArray(32) -const prefix = new StaticArray(32) -const r = new StaticArray(64) -const R = new StaticArray(64) -const k = new StaticArray(64) -const S = new StaticArray(64) -/** - * Sign a message with a private key. The Nano specification uses BLAKE2b as the - * hash function instead of SHA-512 specified by RFC 8032. - * - * In this implementation, both the private key and the public key are required. - * This ensures the user has a full correct keypair for data integrity. It also - * offers a small performance improvement in the case of an invalid public key - * by returning early and avoiding expensive point multiplication; RFC 8032 - * indicates the public key should be recomputed from the private key when - * signing anyway, so there is no penalty for checking key validity first. - * - * https://www.rfc-editor.org/info/rfc8032/#section-5.1.6 - * - * @param {StaticArray} RS 64-byte output buffer for detached signature - * @param {StaticArray} M variable-length message to be signed - * @param {i32} mlen bytelength of `m` - * @param {StaticArray} prv 32-byte private key from input buffer - * @param {StaticArray} pub 32-byte public key from input buffer - */ -export function crypto_sign (RS: StaticArray, M: StaticArray, mlen: i32, prv: StaticArray, pub: StaticArray): i32 { - // Derive `A` from private key - crypto_derive(A, prv) - let c = 0 - for (let i = 0; i < KEY_BYTELENGTH; i++) { - c |= A[i] ^ pub[i] - } - // Return early if `A` does not match public key - if (c != 0) return -1 - - // Hash private key to `h` - blake2b.init().update(prv, KEY_BYTELENGTH).digest(h) - prv.fill(0) - - // Split `h` into clamped secret scalar `s` and nonce prefix - memory.copy(changetype(s), changetype(h), 32) - clamp(s) - memory.copy(changetype(prefix), changetype(h) + 32, 32) - h.fill(0) - - // Hash prefix and message `M` to nonce `r` - blake2b.init().update(prefix, 32).update(M, mlen).digest(r) - prefix.fill(0) - - // Reduce `r` modulo `L`, the group order of the base point `B` - sc_reduce(r) - - // Perform fixed-base scalar multiplication `[r]B`, output to point `R` - ge_scalarmult_base_tobytes(R, r) - - // Compute challenge hash `blake2b(R || A || M)`, output to `k` - blake2b.init().update(R, 32).update(A, 32).update(M, mlen).digest(k) - A.fill(0) - - // Reduce `k` modulo `L` for efficiency - sc_reduce(k) - - // Compute `S = (k * s + r) mod L` - sc_muladd(S, k, s, r) - k.fill(0) - s.fill(0) - r.fill(0) - - // Construct final signature `(R || S)` - memory.copy(changetype(RS), changetype(R), 32) - R.fill(0) - memory.copy(changetype(RS) + 32, changetype(S), 32) - S.fill(0) - - return 0 -} diff --git a/src/assembly/env.ts b/src/assembly/env.ts index 67d0fa9..5f53c2f 100644 --- a/src/assembly/env.ts +++ b/src/assembly/env.ts @@ -45,9 +45,9 @@ export function abort (message: usize, fileName: usize, line: u32, col: u32): vo */ //@ts-expect-error @inline -export function raise (code: i32, a: i32 = 0, b: i32 = 0): void { +export function raise (code: i32, detail: i32 = 0, context: i32 = 0): void { errorCode = code - errorDetail = a - errorContext = b + errorDetail = detail + errorContext = context throw new Error() } diff --git a/src/assembly/index.ts b/src/assembly/index.ts index 74ad5b9..1da0d8a 100644 --- a/src/assembly/index.ts +++ b/src/assembly/index.ts @@ -43,6 +43,23 @@ export function setInputMsgByte (index: i32, v0: i32): void { INPUT_MSG[index] = u8(v0) } +/** Sets 4 bytes in the static message input buffer. */ +export function setInputMsg32 (index: i32, v0: u32, v1: u32, v2: u32, v3: u32, v4: u32, v5: u32, v6: u32, v7: u32): void { + if (index < 0 || MAX_MESSAGE_BYTELENGTH <= index) { + raise(ERROR_INDEX_OUT_OF_RANGE, BUFFER_INPUT_MSG, index) + } + const v = [v0, v1, v2, v3, v4, v5, v6, v7] + for (let i = 0; i < 8; i++) { + if (v[i] < 0 || 0xffffffff < v[i]) { + raise(ERROR_BYTE_OUT_OF_RANGE, BUFFER_INPUT_MSG, v[i]) + } + INPUT_MSG[index + (i << 2)] = u8((v[i] >>> 24) & 255) + INPUT_MSG[index + (i << 2) + 1] = u8((v[i] >>> 16) & 255) + INPUT_MSG[index + (i << 2) + 2] = u8((v[i] >>> 8) & 255) + INPUT_MSG[index + (i << 2) + 3] = u8(v[i] & 255) + } +} + /** Sets a byte in the static private key input buffer (32 bytes). */ export function setInputPrvByte (index: i32, value: i32): void { if (index < 0 || KEY_BYTELENGTH <= index) { @@ -179,7 +196,7 @@ export function sign (mlen: i32): void { // Clear signature output buffer if signing failed if (result != 0) { OUTPUT_SIGN.fill(0) - raise(ERROR_INVALID_PUBLIC_KEY) + raise(ERROR_INVALID_PUBLIC_KEY, result) } } diff --git a/src/lib/errors.ts b/src/lib/errors.ts index a8ce158..a74d7d6 100644 --- a/src/lib/errors.ts +++ b/src/lib/errors.ts @@ -73,7 +73,7 @@ function selftest (code: number, v: number, detail: number, length: number): str function describe (code: number, line: number, detail: number, context: number): string { switch (code) { case errorCodes.ERROR_INVALID_PUBLIC_KEY: - return 'Invalid public key' + return `Invalid public key ${line} ${detail} ${context}` case errorCodes.ERROR_INVALID_MESSAGE_LENGTH: return `Invalid message length: ${detail} is not in [0, ${context}]` case errorCodes.ERROR_INVALID_BLOCK_COUNT: @@ -92,6 +92,6 @@ function describe (code: number, line: number, detail: number, context: number): } /** The module's only import. Takes four integers and never reads wasm memory. */ -export function abort (code: number, line: number, a: number, b: number): never { - throw new Nano25519WasmError(describe(code, line, a, b)) +export function abort (code: number, line: number, detail: number, context: number): never { + throw new Nano25519WasmError(describe(code, line, detail, context)) } diff --git a/src/lib/verify.ts b/src/lib/verify.ts index cccaac6..b87d226 100644 --- a/src/lib/verify.ts +++ b/src/lib/verify.ts @@ -12,8 +12,12 @@ export function verify (sig: unknown, msg: unknown, pub: unknown): boolean { const message = normalize('message', 0, MAX_MESSAGE_BYTELENGTH, msg) const publicKey = normalize('public key', KEY_BYTELENGTH, KEY_BYTELENGTH, pub) const signature = normalize('signature', SIGNATURE_BYTELENGTH, SIGNATURE_BYTELENGTH, sig) - for (let i = 0; i < message.byteLength; i++) { - MemoryBuffers.INPUT_MSG(i, message[i]) + for (let i = 0; i < message.byteLength; i += 32) { + const u32: number[] = [] + for (let j = 0; j < 32; j += 4) { + u32[j >> 2] = ((message[i + j] ?? 0) << 24) | ((message[i + j + 1] ?? 0) << 16) | ((message[i + j + 2] ?? 0) << 8) | (message[i + j + 3] ?? 0) + } + MemoryBuffers.INPUT_MSG_32(i, u32[0], u32[1], u32[2], u32[3], u32[4], u32[5], u32[6], u32[7]) } for (let i = 0; i < KEY_BYTELENGTH; i++) { MemoryBuffers.INPUT_PUB(i, publicKey[i]) diff --git a/src/lib/wasm.ts b/src/lib/wasm.ts index 363ff98..64fb24d 100644 --- a/src/lib/wasm.ts +++ b/src/lib/wasm.ts @@ -13,6 +13,7 @@ type Exports = WebAssembly.Instance['exports'] & { verify: (mlen: number) => void verify_blocks: (count: number) => void setInputMsgByte: (index: number, value: number) => void + setInputMsg32: (index: number, v0: number, v1: number, v2: number, v3: number, v4: number, v5: number, v6: number, v7: number) => void setInputPrvByte: (index: number, value: number) => void setInputPubByte: (index: number, value: number) => void setInputSigByte: (index: number, value: number) => void @@ -99,6 +100,10 @@ export class MemoryBuffers { this.#check() return exports.setInputMsgByte } + static get INPUT_MSG_32 () { + this.#check() + return exports.setInputMsg32 + } static get INPUT_PRV () { this.#check() return exports.setInputPrvByte diff --git a/test/vectors.mjs b/test/vectors.mjs index 698d237..cfe32f0 100644 --- a/test/vectors.mjs +++ b/test/vectors.mjs @@ -1,7 +1,7 @@ /** -* This file is automatically generated during the build process. -* Add or modify test vectors from the files in the test/vectors directory. -*/ + * This file is automatically generated during the build process. + * Add or modify test vectors from the files in the test/vectors directory. + */ // test/vectors/identity.ts var IDENTITY = {