]> zoso.dev Git - libnemo.git/commitdiff
Reorganize compilation into static initialization block.
authorChris Duncan <chris@zoso.dev>
Sun, 15 Dec 2024 07:30:25 +0000 (23:30 -0800)
committerChris Duncan <chris@zoso.dev>
Sun, 15 Dec 2024 07:30:25 +0000 (23:30 -0800)
src/lib/workers/pow.ts

index 1de78ffe96964a4e00bd54f4686a76ee2f11b1ac..27083b8c2b0dee14433cdba73d3bb4605814f3bd 100644 (file)
@@ -2,9 +2,16 @@
 // SPDX-License-Identifier: GPL-3.0-or-later
 
 export class Pow {
+
+       /**
+       * Finds a nonce that satisfies the Nano proof-of-work requirements.
+       *
+       * @param {string} hashHex - Hexadecimal hash of previous block, or public key for new accounts
+       * @param {number} [threshold=0xfffffff8] - Difficulty of proof-of-work calculation
+       */
        static async find (hash: string, threshold: number = this.SEND_THRESHOLD): Promise<string> {
                return new Promise<string>(resolve => {
-                       this.calculate(hash, threshold, resolve)
+                       this.#calculate(hash, resolve, threshold)
                })
        }
 
@@ -14,12 +21,187 @@ export class Pow {
        * Author:  numtel <ben@latenightsketches.com>
        * License: MIT
        */
+       // Vertext Shader
+       static vsSource = `#version 300 es
+precision highp float;
+layout (location=0) in vec4 position;
+layout (location=1) in vec2 uv;
+
+out vec2 uv_pos;
+
+void main() {
+       uv_pos = uv;
+       gl_Position = position;
+}`
+
+       // Fragment shader
+       static fsSource = `#version 300 es
+precision highp float;
+precision highp int;
+
+in vec2 uv_pos;
+out vec4 fragColor;
+
+// Random work values
+// First 2 bytes will be overwritten by texture pixel position
+// Second 2 bytes will be modified if the canvas size is greater than 256x256
+uniform uvec4 u_work0;
+// Last 4 bytes remain as generated externally
+uniform uvec4 u_work1;
+// Precalculated block hash components
+uniform uint blockHash[8];
+// Threshold is 0xfffffff8 for send/change blocks and 0xfffffe for all else
+uniform uint threshold;
+// Defines canvas size
+uniform float workload;
+
+// Defined separately from uint v[32] below as the original value is required
+// to calculate the second uint32 of the digest for threshold comparison
+#define BLAKE2B_IV32_1 0x6A09E667u
+
+// Both buffers represent 16 uint64s as 32 uint32s
+// because that's what GLSL offers, just like Javascript
+
+// Compression buffer, intialized to 2 instances of the initialization vector
+// The following values have been modified from the BLAKE2B_IV:
+// OUTLEN is constant 8 bytes
+// v[0] ^= 0x01010000u ^ uint(OUTLEN);
+// INLEN is constant 40 bytes: work value (8) + block hash (32)
+// v[24] ^= uint(INLEN);
+// It's always the "last" compression at this INLEN
+// v[28] = ~v[28];
+// v[29] = ~v[29];
+uint v[32] = uint[32](
+       0xF2BDC900u, 0x6A09E667u, 0x84CAA73Bu, 0xBB67AE85u,
+       0xFE94F82Bu, 0x3C6EF372u, 0x5F1D36F1u, 0xA54FF53Au,
+       0xADE682D1u, 0x510E527Fu, 0x2B3E6C1Fu, 0x9B05688Cu,
+       0xFB41BD6Bu, 0x1F83D9ABu, 0x137E2179u, 0x5BE0CD19u,
+       0xF3BCC908u, 0x6A09E667u, 0x84CAA73Bu, 0xBB67AE85u,
+       0xFE94F82Bu, 0x3C6EF372u, 0x5F1D36F1u, 0xA54FF53Au,
+       0xADE682F9u, 0x510E527Fu, 0x2B3E6C1Fu, 0x9B05688Cu,
+       0x04BE4294u, 0xE07C2654u, 0x137E2179u, 0x5BE0CD19u
+);
+// Input data buffer
+uint m[32];
+
+// These are offsets into the input data buffer for each mixing step.
+// They are multiplied by 2 from the original SIGMA values in
+// the C reference implementation, which refered to uint64s.
+const int SIGMA82[192] = int[192](
+       0,2,4,6,8,10,12,14,16,18,20,22,24,26,28,30,28,20,8,16,18,30,26,12,2,24,
+       0,4,22,14,10,6,22,16,24,0,10,4,30,26,20,28,6,12,14,2,18,8,14,18,6,2,26,
+       24,22,28,4,12,10,20,8,0,30,16,18,0,10,14,4,8,20,30,28,2,22,24,12,16,6,
+       26,4,24,12,20,0,22,16,6,8,26,14,10,30,28,2,18,24,10,2,30,28,26,8,20,0,
+       14,12,6,18,4,16,22,26,22,14,28,24,2,6,18,10,0,30,8,16,12,4,20,12,30,28,
+       18,22,6,0,16,24,4,26,14,2,8,20,10,20,4,16,8,14,12,2,10,30,22,18,28,6,24,
+       26,0,0,2,4,6,8,10,12,14,16,18,20,22,24,26,28,30,28,20,8,16,18,30,26,12,
+       2,24,0,4,22,14,10,6
+);
+
+// 64-bit unsigned addition within the compression buffer
+// Sets v[a,a+1] += b
+// b0 is the low 32 bits of b, b1 represents the high 32 bits
+void add_uint64 (int a, uint b0, uint b1) {
+       uint o0 = v[a] + b0;
+       uint o1 = v[a + 1] + b1;
+       if (v[a] > 0xFFFFFFFFu - b0) { // did low 32 bits overflow?
+               o1++;
+       }
+       v[a] = o0;
+       v[a + 1] = o1;
+}
+
+// G Mixing function
+void B2B_G (int a, int b, int c, int d, int ix, int iy) {
+       add_uint64(a, v[b], v[b+1]);
+       add_uint64(a, m[ix], m[ix + 1]);
+
+       // v[d,d+1] = (v[d,d+1] xor v[a,a+1]) rotated to the right by 32 bits
+       uint xor0 = v[d] ^ v[a];
+       uint xor1 = v[d + 1] ^ v[a + 1];
+       v[d] = xor1;
+       v[d + 1] = xor0;
+
+       add_uint64(c, v[d], v[d+1]);
+
+       // v[b,b+1] = (v[b,b+1] xor v[c,c+1]) rotated right by 24 bits
+       xor0 = v[b] ^ v[c];
+       xor1 = v[b + 1] ^ v[c + 1];
+       v[b] = (xor0 >> 24) ^ (xor1 << 8);
+       v[b + 1] = (xor1 >> 24) ^ (xor0 << 8);
+
+       add_uint64(a, v[b], v[b+1]);
+       add_uint64(a, m[iy], m[iy + 1]);
+
+       // v[d,d+1] = (v[d,d+1] xor v[a,a+1]) rotated right by 16 bits
+       xor0 = v[d] ^ v[a];
+       xor1 = v[d + 1] ^ v[a + 1];
+       v[d] = (xor0 >> 16) ^ (xor1 << 16);
+       v[d + 1] = (xor1 >> 16) ^ (xor0 << 16);
+
+       add_uint64(c, v[d], v[d+1]);
+
+       // v[b,b+1] = (v[b,b+1] xor v[c,c+1]) rotated right by 63 bits
+       xor0 = v[b] ^ v[c];
+       xor1 = v[b + 1] ^ v[c + 1];
+       v[b] = (xor1 >> 31) ^ (xor0 << 1);
+       v[b + 1] = (xor0 >> 31) ^ (xor1 << 1);
+}
+
+void main() {
+       int i;
+       uint uv_x = uint(uv_pos.x * workload);
+       uint uv_y = uint(uv_pos.y * workload);
+       uint x_pos = uv_x % 256u;
+       uint y_pos = uv_y % 256u;
+       uint x_index = (uv_x - x_pos) / 256u;
+       uint y_index = (uv_y - y_pos) / 256u;
+
+       // First 2 work bytes are the x,y pos within the 256x256 area, the next
+       //  two bytes are modified from the random generated value, XOR'd with
+       //   the x,y area index of where this pixel is located
+       m[0] = (x_pos ^ (y_pos << 8) ^ ((u_work0.b ^ x_index) << 16) ^ ((u_work0.a ^ y_index) << 24));
+       // Remaining bytes are un-modified from the random generated value
+       m[1] = (u_work1.r ^ (u_work1.g << 8) ^ (u_work1.b << 16) ^ (u_work1.a << 24));
+
+       // Block hash
+       m[2] = blockHash[0];
+       m[3] = blockHash[1];
+       m[4] = blockHash[2];
+       m[5] = blockHash[3];
+       m[6] = blockHash[4];
+       m[7] = blockHash[5];
+       m[8] = blockHash[6];
+       m[9] = blockHash[7];
+
+       // twelve rounds of mixing
+       for(i=0;i<12;i++) {
+               B2B_G(0, 8, 16, 24, SIGMA82[i * 16 + 0], SIGMA82[i * 16 + 1]);
+               B2B_G(2, 10, 18, 26, SIGMA82[i * 16 + 2], SIGMA82[i * 16 + 3]);
+               B2B_G(4, 12, 20, 28, SIGMA82[i * 16 + 4], SIGMA82[i * 16 + 5]);
+               B2B_G(6, 14, 22, 30, SIGMA82[i * 16 + 6], SIGMA82[i * 16 + 7]);
+               B2B_G(0, 10, 20, 30, SIGMA82[i * 16 + 8], SIGMA82[i * 16 + 9]);
+               B2B_G(2, 12, 22, 24, SIGMA82[i * 16 + 10], SIGMA82[i * 16 + 11]);
+               B2B_G(4, 14, 16, 26, SIGMA82[i * 16 + 12], SIGMA82[i * 16 + 13]);
+               B2B_G(6, 8, 18, 28, SIGMA82[i * 16 + 14], SIGMA82[i * 16 + 15]);
+       }
+
+       // Threshold test, first 4 bytes not significant,
+       //  only calculate digest of the second 4 bytes
+       if((BLAKE2B_IV32_1 ^ v[1] ^ v[17]) > threshold) {
+               // Success found, return pixel data so work value can be constructed
+               fragColor = vec4(
+                       float(x_index + 1u)/255., // +1 to distinguish from 0 (unsuccessful) pixels
+                       float(y_index + 1u)/255., // Same as previous
+                       float(x_pos)/255., // Return the 2 custom bytes used in work value
+                       float(y_pos)/255.  // Second custom byte
+               );
+       }
+}`
 
        static SEND_THRESHOLD = 0xFFFFFFF8
        /** Used to set canvas size. Must be a multiple of 256. */
-       static WORKLOAD: number = 256 * 4
-       static gl = new OffscreenCanvas(this.WORKLOAD, this.WORKLOAD).getContext('webgl2') as WebGL2RenderingContext
-       static program = this.gl.createProgram()
+       static WORKLOAD: number = 256 * Math.max(1, Math.floor(navigator.hardwareConcurrency / 2))
        static work0 = new Uint8Array(4)
        static work1 = new Uint8Array(4)
        static SIGMA82: number[] = [
@@ -53,181 +235,6 @@ export class Pow {
                        this.B2B_G_params.push(this.SIGMA82[i * 16 + 15])
                }
        }
-       // Vertext Shader
-       static vsSource = `#version 300 es
-       precision highp float;
-       layout (location=0) in vec4 position;
-       layout (location=1) in vec2 uv;
-
-       out vec2 uv_pos;
-
-       void main() {
-               uv_pos = uv;
-               gl_Position = position;
-       }`
-
-       // Fragment shader
-       static fsSource = `#version 300 es
-       precision highp float;
-       precision highp int;
-
-       in vec2 uv_pos;
-       out vec4 fragColor;
-
-       // Random work values
-       // First 2 bytes will be overwritten by texture pixel position
-       // Second 2 bytes will be modified if the canvas size is greater than 256x256
-       uniform uvec4 u_work0;
-       // Last 4 bytes remain as generated externally
-       uniform uvec4 u_work1;
-       // Precalculated block hash components
-       uniform uint blockHash[8];
-       // Threshold is 0xfffffff8 for send/change blocks and 0xfffffe for all else
-       uniform uint threshold;
-
-       // Defined separately from uint v[32] below as the original value is required
-       // to calculate the second uint32 of the digest for threshold comparison
-       #define BLAKE2B_IV32_1 0x6A09E667u
-
-       // Both buffers represent 16 uint64s as 32 uint32s
-       // because that's what GLSL offers, just like Javascript
-
-       // Compression buffer, intialized to 2 instances of the initialization vector
-       // The following values have been modified from the BLAKE2B_IV:
-       // OUTLEN is constant 8 bytes
-       // v[0] ^= 0x01010000u ^ uint(OUTLEN);
-       // INLEN is constant 40 bytes: work value (8) + block hash (32)
-       // v[24] ^= uint(INLEN);
-       // It's always the "last" compression at this INLEN
-       // v[28] = ~v[28];
-       // v[29] = ~v[29];
-       uint v[32] = uint[32](
-               0xF2BDC900u, 0x6A09E667u, 0x84CAA73Bu, 0xBB67AE85u,
-               0xFE94F82Bu, 0x3C6EF372u, 0x5F1D36F1u, 0xA54FF53Au,
-               0xADE682D1u, 0x510E527Fu, 0x2B3E6C1Fu, 0x9B05688Cu,
-               0xFB41BD6Bu, 0x1F83D9ABu, 0x137E2179u, 0x5BE0CD19u,
-               0xF3BCC908u, 0x6A09E667u, 0x84CAA73Bu, 0xBB67AE85u,
-               0xFE94F82Bu, 0x3C6EF372u, 0x5F1D36F1u, 0xA54FF53Au,
-               0xADE682F9u, 0x510E527Fu, 0x2B3E6C1Fu, 0x9B05688Cu,
-               0x04BE4294u, 0xE07C2654u, 0x137E2179u, 0x5BE0CD19u
-       );
-       // Input data buffer
-       uint m[32];
-
-       // These are offsets into the input data buffer for each mixing step.
-       // They are multiplied by 2 from the original SIGMA values in
-       // the C reference implementation, which refered to uint64s.
-       const int SIGMA82[192] = int[192](
-               0,2,4,6,8,10,12,14,16,18,20,22,24,26,28,30,28,20,8,16,18,30,26,12,2,24,
-               0,4,22,14,10,6,22,16,24,0,10,4,30,26,20,28,6,12,14,2,18,8,14,18,6,2,26,
-               24,22,28,4,12,10,20,8,0,30,16,18,0,10,14,4,8,20,30,28,2,22,24,12,16,6,
-               26,4,24,12,20,0,22,16,6,8,26,14,10,30,28,2,18,24,10,2,30,28,26,8,20,0,
-               14,12,6,18,4,16,22,26,22,14,28,24,2,6,18,10,0,30,8,16,12,4,20,12,30,28,
-               18,22,6,0,16,24,4,26,14,2,8,20,10,20,4,16,8,14,12,2,10,30,22,18,28,6,24,
-               26,0,0,2,4,6,8,10,12,14,16,18,20,22,24,26,28,30,28,20,8,16,18,30,26,12,
-               2,24,0,4,22,14,10,6
-       );
-
-       // 64-bit unsigned addition within the compression buffer
-       // Sets v[a,a+1] += b
-       // b0 is the low 32 bits of b, b1 represents the high 32 bits
-       void add_uint64 (int a, uint b0, uint b1) {
-               uint o0 = v[a] + b0;
-               uint o1 = v[a + 1] + b1;
-               if (v[a] > 0xFFFFFFFFu - b0) { // did low 32 bits overflow?
-                       o1++;
-               }
-               v[a] = o0;
-               v[a + 1] = o1;
-       }
-
-       // G Mixing function
-       void B2B_G (int a, int b, int c, int d, int ix, int iy) {
-               add_uint64(a, v[b], v[b+1]);
-               add_uint64(a, m[ix], m[ix + 1]);
-
-               // v[d,d+1] = (v[d,d+1] xor v[a,a+1]) rotated to the right by 32 bits
-               uint xor0 = v[d] ^ v[a];
-               uint xor1 = v[d + 1] ^ v[a + 1];
-               v[d] = xor1;
-               v[d + 1] = xor0;
-
-               add_uint64(c, v[d], v[d+1]);
-
-               // v[b,b+1] = (v[b,b+1] xor v[c,c+1]) rotated right by 24 bits
-               xor0 = v[b] ^ v[c];
-               xor1 = v[b + 1] ^ v[c + 1];
-               v[b] = (xor0 >> 24) ^ (xor1 << 8);
-               v[b + 1] = (xor1 >> 24) ^ (xor0 << 8);
-
-               add_uint64(a, v[b], v[b+1]);
-               add_uint64(a, m[iy], m[iy + 1]);
-
-               // v[d,d+1] = (v[d,d+1] xor v[a,a+1]) rotated right by 16 bits
-               xor0 = v[d] ^ v[a];
-               xor1 = v[d + 1] ^ v[a + 1];
-               v[d] = (xor0 >> 16) ^ (xor1 << 16);
-               v[d + 1] = (xor1 >> 16) ^ (xor0 << 16);
-
-               add_uint64(c, v[d], v[d+1]);
-
-               // v[b,b+1] = (v[b,b+1] xor v[c,c+1]) rotated right by 63 bits
-               xor0 = v[b] ^ v[c];
-               xor1 = v[b + 1] ^ v[c + 1];
-               v[b] = (xor1 >> 31) ^ (xor0 << 1);
-               v[b + 1] = (xor0 >> 31) ^ (xor1 << 1);
-       }
-
-       void main() {
-               int i;
-               uint uv_x = uint(uv_pos.x * ${this.WORKLOAD - 1}.);
-               uint uv_y = uint(uv_pos.y * ${this.WORKLOAD - 1}.);
-               uint x_pos = uv_x % 256u;
-               uint y_pos = uv_y % 256u;
-               uint x_index = (uv_x - x_pos) / 256u;
-               uint y_index = (uv_y - y_pos) / 256u;
-
-               // First 2 work bytes are the x,y pos within the 256x256 area, the next
-               //  two bytes are modified from the random generated value, XOR'd with
-               //   the x,y area index of where this pixel is located
-               m[0] = (x_pos ^ (y_pos << 8) ^ ((u_work0.b ^ x_index) << 16) ^ ((u_work0.a ^ y_index) << 24));
-               // Remaining bytes are un-modified from the random generated value
-               m[1] = (u_work1.r ^ (u_work1.g << 8) ^ (u_work1.b << 16) ^ (u_work1.a << 24));
-
-               // Block hash
-               m[2] = blockHash[0];
-               m[3] = blockHash[1];
-               m[4] = blockHash[2];
-               m[5] = blockHash[3];
-               m[6] = blockHash[4];
-               m[7] = blockHash[5];
-               m[8] = blockHash[6];
-               m[9] = blockHash[7];
-
-               // twelve rounds of mixing
-               for(i=0;i<12;i++) {
-                       B2B_G(0, 8, 16, 24, SIGMA82[i * 16 + 0], SIGMA82[i * 16 + 1]);
-                       B2B_G(2, 10, 18, 26, SIGMA82[i * 16 + 2], SIGMA82[i * 16 + 3]);
-                       B2B_G(4, 12, 20, 28, SIGMA82[i * 16 + 4], SIGMA82[i * 16 + 5]);
-                       B2B_G(6, 14, 22, 30, SIGMA82[i * 16 + 6], SIGMA82[i * 16 + 7]);
-                       B2B_G(0, 10, 20, 30, SIGMA82[i * 16 + 8], SIGMA82[i * 16 + 9]);
-                       B2B_G(2, 12, 22, 24, SIGMA82[i * 16 + 10], SIGMA82[i * 16 + 11]);
-                       B2B_G(4, 14, 16, 26, SIGMA82[i * 16 + 12], SIGMA82[i * 16 + 13]);
-                       B2B_G(6, 8, 18, 28, SIGMA82[i * 16 + 14], SIGMA82[i * 16 + 15]);
-               }
-
-               // Threshold test, first 4 bytes not significant,
-               //  only calculate digest of the second 4 bytes
-               if((BLAKE2B_IV32_1 ^ v[1] ^ v[17]) > threshold) {
-                       // Success found, return pixel data so work value can be constructed
-                       fragColor = vec4(
-                               float(x_index + 1u)/255., // +1 to distinguish from 0 (unsuccessful) pixels
-                               float(y_index + 1u)/255., // Same as previous
-                               float(x_pos)/255., // Return the 2 custom bytes used in work value
-                               float(y_pos)/255.  // Second custom byte
-                       );
-               }
-       }`
 
 
        static hexify (arr: number[] | Uint8Array): string {
@@ -251,83 +258,88 @@ export class Pow {
        }
 
        /*
-               for (i = 0; i<12; i++) {
-                       B2B_G(0, 8, 16, 24, SIGMA82[i * 16 + 0], SIGMA82[i * 16 + 1]);
-                       ->
-                       B2B_G(0, 8, 16, 24, SIGMA82[i * 16 + 0], SIGMA82[i * 16 + 1]);
+       for (i = 0; i<12; i++) {
+       B2B_G(0, 8, 16, 24, SIGMA82[i * 16 + 0], SIGMA82[i * 16 + 1]);
+       ->
+       B2B_G(0, 8, 16, 24, SIGMA82[i * 16 + 0], SIGMA82[i * 16 + 1]);
        */
 
+       static gl: WebGL2RenderingContext | null
+       static program: WebGLProgram | null
+       static work0Location: WebGLUniformLocation | null
+       static work1Location: WebGLUniformLocation | null
+       static blockHashLocation: WebGLUniformLocation | null
+       static thresholdLocation: WebGLUniformLocation | null
+       static workloadLocation: WebGLUniformLocation | null
+       static vertexShader: WebGLShader | null
+       static fragmentShader: WebGLShader | null
+       static positionBuffer: WebGLBuffer | null
+       static uvBuffer: WebGLBuffer | null
+       // Vertex Positions, 2 triangles
+       static positions = new Float32Array([
+               -1, -1, 0, -1, 1, 0, 1, 1, 0,
+               1, -1, 0, 1, 1, 0, -1, -1, 0
+       ])
+       // Texture Positions
+       static uvPosArray = new Float32Array([
+               1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1
+       ])
+
+       // Compile
        static {
-
-               const vertexShader = this.gl.createShader(this.gl.VERTEX_SHADER)
-               if (vertexShader == null)
-                       throw 'error creating vertex shader'
-               this.gl.shaderSource(vertexShader, this.vsSource)
-               this.gl.compileShader(vertexShader)
-
-               if (!this.gl.getShaderParameter(vertexShader, this.gl.COMPILE_STATUS))
-                       throw this.gl.getShaderInfoLog(vertexShader)
-
-               const fragmentShader = this.gl.createShader(this.gl.FRAGMENT_SHADER)
-               if (fragmentShader == null)
-                       throw 'error creating fragment shader'
-               this.gl.shaderSource(fragmentShader, this.fsSource)
-               this.gl.compileShader(fragmentShader)
-
-               if (!this.gl.getShaderParameter(fragmentShader, this.gl.COMPILE_STATUS))
-                       throw this.gl.getShaderInfoLog(fragmentShader)
-
-               this.gl.attachShader(this.program, vertexShader)
-               this.gl.attachShader(this.program, fragmentShader)
+               this.gl = new OffscreenCanvas(this.WORKLOAD, this.WORKLOAD).getContext('webgl2')
+               if (this.gl == null) throw new Error('WebGL 2 is required')
+
+               this.program = this.gl.createProgram()
+               if (this.program == null) throw new Error('Failed to create shader program')
+
+               this.vertexShader = this.gl.createShader(this.gl.VERTEX_SHADER)
+               if (this.vertexShader == null) throw new Error('Failed to create vertex shader')
+               this.gl.shaderSource(this.vertexShader, this.vsSource)
+               this.gl.compileShader(this.vertexShader)
+               if (!this.gl.getShaderParameter(this.vertexShader, this.gl.COMPILE_STATUS))
+                       throw new Error(this.gl.getShaderInfoLog(this.vertexShader) ?? `Failed to compile vertex shader`)
+
+               this.fragmentShader = this.gl.createShader(this.gl.FRAGMENT_SHADER)
+               if (this.fragmentShader == null) throw new Error('Failed to create fragment shader')
+               this.gl.shaderSource(this.fragmentShader, this.fsSource)
+               this.gl.compileShader(this.fragmentShader)
+               if (!this.gl.getShaderParameter(this.fragmentShader, this.gl.COMPILE_STATUS))
+                       throw new Error(this.gl.getShaderInfoLog(this.fragmentShader) ?? `Failed to compile fragment shader`)
+
+               this.gl.attachShader(this.program, this.vertexShader)
+               this.gl.attachShader(this.program, this.fragmentShader)
                this.gl.linkProgram(this.program)
-
                if (!this.gl.getProgramParameter(this.program, this.gl.LINK_STATUS))
-                       throw this.gl.getProgramInfoLog(this.program)
-
-               this.gl.useProgram(this.program)
+                       throw new Error(this.gl.getProgramInfoLog(this.program) ?? `Failed to link program`)
 
                // Construct simple 2D geometry
+               this.gl.useProgram(this.program)
                const triangleArray = this.gl.createVertexArray()
                this.gl.bindVertexArray(triangleArray)
 
-               // Vertex Positions, 2 triangles
-               const positions = new Float32Array([
-                       -1, -1, 0, -1, 1, 0, 1, 1, 0,
-                       1, -1, 0, 1, 1, 0, -1, -1, 0
-               ])
-               const positionBuffer = this.gl.createBuffer()
-               this.gl.bindBuffer(this.gl.ARRAY_BUFFER, positionBuffer)
-               this.gl.bufferData(this.gl.ARRAY_BUFFER, positions, this.gl.STATIC_DRAW)
+               this.positionBuffer = this.gl.createBuffer()
+               this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.positionBuffer)
+               this.gl.bufferData(this.gl.ARRAY_BUFFER, this.positions, this.gl.STATIC_DRAW)
                this.gl.vertexAttribPointer(0, 3, this.gl.FLOAT, false, 0, 0)
                this.gl.enableVertexAttribArray(0)
 
-               // Texture Positions
-               const uvPosArray = new Float32Array([
-                       1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1
-               ])
                const uvBuffer = this.gl.createBuffer()
                this.gl.bindBuffer(this.gl.ARRAY_BUFFER, uvBuffer)
-               this.gl.bufferData(this.gl.ARRAY_BUFFER, uvPosArray, this.gl.STATIC_DRAW)
+               this.gl.bufferData(this.gl.ARRAY_BUFFER, this.uvPosArray, this.gl.STATIC_DRAW)
                this.gl.vertexAttribPointer(1, 2, this.gl.FLOAT, false, 0, 0)
                this.gl.enableVertexAttribArray(1)
-       }
-       static work0Location = this.gl.getUniformLocation(this.program, 'u_work0')
-       static work1Location = this.gl.getUniformLocation(this.program, 'u_work1')
-       static blockHashLocation = this.gl.getUniformLocation(this.program, "blockHash")
-       static thresholdLocation = this.gl.getUniformLocation(this.program, "threshold")
 
+               this.work0Location = this.gl.getUniformLocation(this.program, 'u_work0')
+               this.work1Location = this.gl.getUniformLocation(this.program, 'u_work1')
+               this.blockHashLocation = this.gl.getUniformLocation(this.program, "blockHash")
+               this.thresholdLocation = this.gl.getUniformLocation(this.program, "threshold")
+               this.workloadLocation = this.gl.getUniformLocation(this.program, "workload")
+       }
 
-       /**
-       * self.NanoWebglPow (hashHex, threshold, callback)
-       * @param {string} hashHex - Previous Block Hash as Hex String
-       * @param {string} threshold - Optional difficulty threshold(default=0xFFFFFFF8 since v21)
-       * @param {function} callback - Called when work value found
-       * Receives single string argument, work value as hex
-       */
-       static calculate (hashHex: string, threshold: number = this.SEND_THRESHOLD, callback: (nonce: string | PromiseLike<string>) => any): void {
-               if (typeof threshold !== 'number') throw new TypeError(`invalid_threshold ${threshold}`)
-               if (!/^[A-F-a-f0-9]{64}$/.test(hashHex)) throw new Error(`invalid_hash ${hashHex}`)
-               if (this.gl == null) throw new Error('webgl2_required')
+       static #calculate (hashHex: string, callback: (nonce: string | PromiseLike<string>) => any, threshold: number = this.SEND_THRESHOLD): void {
+               if (typeof threshold !== 'number') throw new TypeError(`Invalid threshold ${threshold}`)
+               if (this.gl == null) throw new Error('WebGL 2 is required')
                this.gl.clearColor(0, 0, 0, 1)
                const hashBytes: Uint32Array = this.processHash(hashHex)
 
@@ -345,11 +357,12 @@ export class Pow {
                        Pow.gl.uniform4uiv(Pow.work1Location, this.work1)
                        Pow.gl.uniform1uiv(Pow.blockHashLocation, hashBytes)
                        Pow.gl.uniform1ui(Pow.thresholdLocation, threshold)
+                       Pow.gl.uniform1f(Pow.workloadLocation, this.WORKLOAD - 1)
 
-                       this.gl.clear(this.gl.COLOR_BUFFER_BIT)
-                       this.gl.drawArrays(this.gl.TRIANGLES, 0, 6)
-                       const pixels = new Uint8Array(this.gl.drawingBufferWidth * this.gl.drawingBufferHeight * 4)
-                       this.gl.readPixels(0, 0, this.gl.drawingBufferWidth, this.gl.drawingBufferHeight, this.gl.RGBA, this.gl.UNSIGNED_BYTE, pixels)
+                       Pow.gl.clear(Pow.gl.COLOR_BUFFER_BIT)
+                       Pow.gl.drawArrays(Pow.gl.TRIANGLES, 0, 6)
+                       const pixels = new Uint8Array(Pow.gl.drawingBufferWidth * Pow.gl.drawingBufferHeight * 4)
+                       Pow.gl.readPixels(0, 0, Pow.gl.drawingBufferWidth, Pow.gl.drawingBufferHeight, Pow.gl.RGBA, Pow.gl.UNSIGNED_BYTE, pixels)
                        // Check the pixels for any success
                        for (let i = 0; i < pixels.length; i += 4) {
                                if (pixels[i] !== 0) {