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<u8> = new StaticArray<u8>(${B_LENGTH})
- c: u8 = 0
- h: StaticArray<u64> = new StaticArray<u64>(${H_LENGTH})
- p: StaticArray<u8> = new StaticArray<u8>(64)
- t: u64 = 0
-
- ${COMPRESS()}
- `
+ 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<u8> = new StaticArray<u8>(${B_LENGTH})
+ c: u8 = 0
+ h: StaticArray<u64> = new StaticArray<u64>(${H_LENGTH})
+ p: StaticArray<u8> = new StaticArray<u8>(64)
+ t: u64 = 0
+`
return output
})()
function CLEAR () {
- let output = `
- this.b.fill(0)
+ 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
- `
+ 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
const p_ptr = changetype<usize>(this.p)
- `
+`
for (let i = 0; i < H_LENGTH; i++) {
output += `
- this.h[${i}] = ${blake2b_iv[i]} ^ load<u64>(p_ptr, ${i << 3})
- `
+ this.h[${i}] = ${blake2b_iv[i]} ^ load<u64>(p_ptr, ${i << 3})`
}
output += `
+
return this
- }
- `
+ }`
return output
})()
v${d} = rotr<u64>(v${d} ^ v${a}, 16)
v${c} = v${c} + v${d}
v${b} = rotr<u64>(v${b} ^ v${c}, 63)
- `
+`
}
/**
*/
function ROUND (r) {
let output = `
- // ROUND ${r}
- `
+ // ROUND ${r}`
for (let i = 0; i < 8; i++) {
const a = blake2b_state[i][0]
const b = blake2b_state[i][1]
* 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
+ * @type {string} API-specific code executing 12 rounds of G mixing
*/
-function COMPRESS () {
+const COMPRESS = (() => {
let output = `
- // COMPRESS
- // Defined in BLAKE2 section 2.4
- compress (src: usize, isFinal: u64): void {
+ // COMPRESS
+ // Defined in BLAKE2 section 2.4
+ compress (src: usize, 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
- `
+ // - 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]}
- `
+ 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
// flip bits if "last block" flag is set so that v14 = ~v14
v${14} ^= isFinal
- // copy input buffer to message block
- `
+ // copy input buffer to message block`
for (let i = 0; i < 16; i++) {
output += `
- let m${i}: u64 = load<u64>(src + ${i << 3})
- `
+ let m${i}: u64 = load<u64>(src + ${i << 3})`
}
output += `
+
// twelve rounds of mixing
- `
+`
for (let r = 0; r < 12; r++) {
output += ROUND(r)
}
output += `
- // set new chain value
- `
+ // set new chain value`
for (let i = 0; i < H_LENGTH; i++) {
output += `
- this.h[${i}] ^= v${i} ^ v${i + H_LENGTH}
- `
+ this.h[${i}] ^= v${i} ^ v${i + H_LENGTH}`
}
output += `
- }
- `
+ }
+`
return output
-}
+})()
const BLAKE2b = `//! SPDX-FileCopyrightText: 2026 Chris Duncan <chris@codecow.com>
//! SPDX-License-Identifier: GPL-3.0-or-later
export class Blake2b {
- ${DECLARATIONS}
- ${INIT}
+${DECLARATIONS}
+${INIT}
+${COMPRESS}
// input: variable-length message bytes passed by user to be hashed
update (input: StaticArray<u8>, length: i32): Blake2b {
const input_ptr: usize = changetype<usize>(input)
memory.copy(changetype<usize>(output), changetype<usize>(this.h), 64)
// clear internal buffers and counters
- ${CLEAR()}
+${CLEAR()}
+ return
}
}
`
//! SPDX-License-Identifier: GPL-3.0-or-later
export class Blake2b {
-
/**
* Buffers and counters
* - b: input buffer, filled and refilled by input data in 128 byte increments
p: StaticArray<u8> = new StaticArray<u8>(64)
t: u64 = 0
+ init (): Blake2b {
+ // reset buffers and counters
+ this.b.fill(0)
+ this.c = 0
+ this.h.fill(0)
+ this.p.fill(0)
+ 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
+ const p_ptr = changetype<usize>(this.p)
+
+ this.h[0] = 0x6a09e667f3bcc908 ^ load<u64>(p_ptr, 0)
+ this.h[1] = 0xbb67ae8584caa73b ^ load<u64>(p_ptr, 8)
+ this.h[2] = 0x3c6ef372fe94f82b ^ load<u64>(p_ptr, 16)
+ this.h[3] = 0xa54ff53a5f1d36f1 ^ load<u64>(p_ptr, 24)
+ this.h[4] = 0x510e527fade682d1 ^ load<u64>(p_ptr, 32)
+ this.h[5] = 0x9b05688c2b3e6c1f ^ load<u64>(p_ptr, 40)
+ this.h[6] = 0x1f83d9abfb41bd6b ^ load<u64>(p_ptr, 48)
+ this.h[7] = 0x5be0cd19137e2179 ^ load<u64>(p_ptr, 56)
+
+ return this
+ }
// COMPRESS
// Defined in BLAKE2 section 2.4
// 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
v14 ^= isFinal
// copy input buffer to message block
-
let m0: u64 = load<u64>(src + 0)
-
let m1: u64 = load<u64>(src + 8)
-
let m2: u64 = load<u64>(src + 16)
-
let m3: u64 = load<u64>(src + 24)
-
let m4: u64 = load<u64>(src + 32)
-
let m5: u64 = load<u64>(src + 40)
-
let m6: u64 = load<u64>(src + 48)
-
let m7: u64 = load<u64>(src + 56)
-
let m8: u64 = load<u64>(src + 64)
-
let m9: u64 = load<u64>(src + 72)
-
let m10: u64 = load<u64>(src + 80)
-
let m11: u64 = load<u64>(src + 88)
-
let m12: u64 = load<u64>(src + 96)
-
let m13: u64 = load<u64>(src + 104)
-
let m14: u64 = load<u64>(src + 112)
-
let m15: u64 = load<u64>(src + 120)
// twelve rounds of mixing
// ROUND 0
-
v0 = v0 + v4 + m0
v12 = rotr<u64>(v12 ^ v0, 32)
v8 = v8 + v12
v4 = rotr<u64>(v4 ^ v9, 63)
// ROUND 1
-
v0 = v0 + v4 + m14
v12 = rotr<u64>(v12 ^ v0, 32)
v8 = v8 + v12
v4 = rotr<u64>(v4 ^ v9, 63)
// ROUND 2
-
v0 = v0 + v4 + m11
v12 = rotr<u64>(v12 ^ v0, 32)
v8 = v8 + v12
v4 = rotr<u64>(v4 ^ v9, 63)
// ROUND 3
-
v0 = v0 + v4 + m7
v12 = rotr<u64>(v12 ^ v0, 32)
v8 = v8 + v12
v4 = rotr<u64>(v4 ^ v9, 63)
// ROUND 4
-
v0 = v0 + v4 + m9
v12 = rotr<u64>(v12 ^ v0, 32)
v8 = v8 + v12
v4 = rotr<u64>(v4 ^ v9, 63)
// ROUND 5
-
v0 = v0 + v4 + m2
v12 = rotr<u64>(v12 ^ v0, 32)
v8 = v8 + v12
v4 = rotr<u64>(v4 ^ v9, 63)
// ROUND 6
-
v0 = v0 + v4 + m12
v12 = rotr<u64>(v12 ^ v0, 32)
v8 = v8 + v12
v4 = rotr<u64>(v4 ^ v9, 63)
// ROUND 7
-
v0 = v0 + v4 + m13
v12 = rotr<u64>(v12 ^ v0, 32)
v8 = v8 + v12
v4 = rotr<u64>(v4 ^ v9, 63)
// ROUND 8
-
v0 = v0 + v4 + m6
v12 = rotr<u64>(v12 ^ v0, 32)
v8 = v8 + v12
v4 = rotr<u64>(v4 ^ v9, 63)
// ROUND 9
-
v0 = v0 + v4 + m10
v12 = rotr<u64>(v12 ^ v0, 32)
v8 = v8 + v12
v4 = rotr<u64>(v4 ^ v9, 63)
// ROUND 10
-
v0 = v0 + v4 + m0
v12 = rotr<u64>(v12 ^ v0, 32)
v8 = v8 + v12
v4 = rotr<u64>(v4 ^ v9, 63)
// ROUND 11
-
v0 = v0 + v4 + m14
v12 = rotr<u64>(v12 ^ v0, 32)
v8 = v8 + v12
v4 = rotr<u64>(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.p.fill(0)
- 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
-
- 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<u64>(changetype<usize>(this.p) + (i << 3))
- }
- return this
}
// input: variable-length message bytes passed by user to be hashed
memory.copy(changetype<usize>(output), changetype<usize>(this.h), 64)
// clear internal buffers and counters
-
this.b.fill(0)
this.c = 0
this.h.fill(0)
this.p.fill(0)
this.t = 0
+ return
}
}