]> zoso.dev Git - nano-pow.git/commitdiff
Set up for v4
authorChris Duncan <chris@zoso.dev>
Thu, 20 Mar 2025 21:56:55 +0000 (14:56 -0700)
committerChris Duncan <chris@zoso.dev>
Thu, 20 Mar 2025 21:56:55 +0000 (14:56 -0700)
Add two new methods which align with Nano node RPC actions. Return more data from hash result. Indicate a more specific `threshold` by passing a full 16-character string to the new `work_` methods. Changed `NanoPowGl.size` property to return total pixel count. Add typings for request and response objects consumed by new `work_` methods. Add more tests for receive threshold edge cases. Improve build process. Expand documentation.

17 files changed:
.gitignore
CHANGELOG.md
esbuild.mjs
package-lock.json
package.json
src/bin/cli.ts [moved from src/bin/cli.js with 96% similarity]
src/lib/gl/gl-downsample.frag [moved from src/lib/gl/gl-downsample.ts with 93% similarity]
src/lib/gl/gl-draw.frag [moved from src/lib/gl/gl-draw.ts with 94% similarity]
src/lib/gl/gl-vertex.ts [deleted file]
src/lib/gl/gl-vertex.vert [new file with mode: 0644]
src/lib/gl/index.ts
src/lib/gpu/compute.wgsl
src/lib/gpu/index.ts
src/main.ts
src/types.d.ts
test/blockhashes.txt
test/index.html

index 657bd01f944941ccb7abb3822ddf0758c42f4fd2..a3dac34d01e1565e8fcd09a7ce082f34dcbc19d6 100644 (file)
@@ -19,3 +19,7 @@ node_modules/
 build/\r
 dist/\r
 types/\r
+\r
+# IDE\r
+.vs/\r
+.vscode/\r
index 9589ab8b41187c19ba6ca120109fe1a6161b6bb6..cb1a02b4a58a07aea5e6dea7fe7f3d6a099c52fd 100644 (file)
@@ -221,3 +221,146 @@ Add Typescript typings for new WebGL types.
 Fix minor issues with test page.
 
 Add benchmark results table.
+
+
+
+## v2.0.0
+
+### Notable Changes
+
+#### Control behavior with configuration options
+
+While NanoPow currently supports configuration of a custom minimum threshold in
+order to support send, receive, and legacy level of difficulty, additional
+options allow developers to control behavior according to their needs.
+
+Instead of a threshold parameter, `search` and `validate` accept a new options
+object parameter.
+
+```javascript
+NanoPow.search(blockhash, { threshold: 'fffffc00', effort: 16, debug: true })
+```
+
+**This is a breaking change.** Integrations already using the optional threshold
+parameter should update calls to pass { threshold: <value> } instead.
+
+#### Align with reference implementation of BLAKE2b
+
+Nano uses the BLAKE2b hashing algorithm for calculating proof-of-work and other
+operations. The reference implementation of BLAKE2b uses 64-bit unsigned integer
+operands, but WebGL and WebGPU only support 32-bit uints. Previous designs used
+pairs of `u32` variables to represent high and low bits of a single `u64`.
+
+Now, the same approach is used but implemented with vectors instead of scalars,
+i.e. `vec2<u32>` instead of `u32`. This has several benefits:
+* Integer pairs of high and low bits are held in memory together, improving
+legibility as well as alignment with the original C reference implementation.
+* Arithmetic and bitwise operations are executed on both high and low bits in
+parallel using one statement instead of three.
+* File size is reduced by over 58%.
+
+### Other Changes
+
+Expand the options available on the sample testing page.
+
+Fix a bug with the validate function where a valid but different nonce would be
+reported as a match to the input nonce.
+
+Fix a bug with the truncated average benchmark calculation.
+
+
+
+## v1.2.4
+
+### Notable Changes
+
+Fix duplicate code on testing page due to bad merge commit.
+
+Increase WebGPU dispatch size.
+
+
+
+## v1.2.3
+
+### Notable Changes
+
+Add bespoke TypeScript definition file.
+
+Remove usage of package `imports` property and use regular relative paths.
+
+
+
+## v1.2.2
+
+### Notable Changes
+
+Compile to `dist/` instead of `build/`.
+
+
+
+## v1.2.1
+
+### Notable Changes
+
+Rename bundle output file.
+
+
+
+## v1.2.0
+
+### Notable Changes
+
+#### Improved fallback behavior
+
+Integrating a tool like NanoPow should be as painless as possible, even when
+using it in unsupported environments.
+
+Now, browser support for WebGPU and WebGL is checked and the faster API set to
+the general `NanoPow` module, so there is no need anymore to import both
+implementations and choose manually, although that is still an option if
+desired.
+
+### Other Changes
+
+Clarify the README with better instructions.
+
+Provide additional context about test progress on the testing webpage.
+
+
+
+## v1.1.0
+
+### Notable Changes
+
+#### Validate existing work
+
+Test a previously calculated PoW value against a block hash to check if it meets
+Nano work threshold requirements.
+
+#### Other Changes
+
+Fix packaging bugs to eliminate npm errors about imports and types.
+
+Fix repo cloning so that `git clone https://zoso.dev/nano-pow.git` works as
+expected (thanks to u/the_azarian for helping track this down).
+
+
+
+## v1.0.0
+
+### Notable Changes
+
+#### NanoPow released!
+
+Written in Javascript and WGSL, NanoPow leverages the cutting edge WebGPU API to
+achieve massively increased proof-of-work speed for supported devices directly
+in the browser!
+
+* Faster than any other JavaScript implementation of Nano proof-of-work.
+* Avoids graphical stuttering seen in WebGL implementations.
+* Works entirely offline and locally on the device.
+* Zero external dependencies. Run it in any environment that supports WebGPU
+compute shaders.
+* Released under the GPLv3 license to promote user freedom and FOSS principles.
+
+Full announcement: https://www.reddit.com/r/nanocurrency/comments/1hz841f/announcing_nanopow_local_proofofwork_at/
index 2ccecf6c44b54040583cdf683f8f75d5fee2e5ac..14a0ba3f5dd67a7cccdf6f2b502dee4cef8d3d88 100644 (file)
@@ -8,15 +8,32 @@ await build({
        bundle: true,
        platform: 'browser',
        entryPoints: [
-               { out: 'main.min', in: './src/main.js' }
+               { in: './src/main.ts', out: 'main.min' },
+               { in: './src/types.d.ts', out: 'types.d' }
        ],
+       loader: {
+               '.d.ts': 'copy'
+       },
        format: 'esm',
        legalComments: 'inline',
        outdir: 'dist',
        target: 'esnext',
        plugins: [
                glsl({
-                       minify: true
+                       minify: true,
+                       preserveLegalComments: true
                })
        ]
 })
+
+await build({
+       bundle: false,
+       platform: 'node',
+       entryPoints: [
+               './src/bin/*.ts'
+       ],
+       format: 'esm',
+       legalComments: 'inline',
+       outdir: 'dist/bin',
+       target: 'esnext'
+})
index 40118969f141e2e2f2c2166d190972b337c33708..bb9e90f02a6d791cb3b2c38f6ef76f031bd1b4dd 100644 (file)
@@ -13,9 +13,9 @@
                        },
                        "devDependencies": {
                                "@types/node": "^22.13.10",
-                               "@webgpu/types": "^0.1.55",
+                               "@webgpu/types": "^0.1.57",
                                "esbuild": "^0.25.1",
-                               "esbuild-plugin-glsl": "^1.2.3",
+                               "esbuild-plugin-glsl": "^1.4.0",
                                "typescript": "^5.8.2"
                        },
                        "funding": {
                        }
                },
                "node_modules/@webgpu/types": {
-                       "version": "0.1.55",
-                       "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.55.tgz",
-                       "integrity": "sha512-p97I8XEC1h04esklFqyIH+UhFrUcj8/1/vBWgc6lAK4jMJc+KbhUy8D4dquHYztFj6pHLqGcp/P1xvBBF4r3DA==",
+                       "version": "0.1.57",
+                       "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.57.tgz",
+                       "integrity": "sha512-w8IuWOmgeb9bA9swqSyG6Z/KdKfCdGPxZ75YJUpsJBF4Q/RT5rnPuStzuxOXihcrSjGO4/maH+M9PQgbBVs69A==",
                        "dev": true,
                        "license": "BSD-3-Clause"
                },
                        }
                },
                "node_modules/bare-os": {
-                       "version": "3.5.1",
-                       "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.5.1.tgz",
-                       "integrity": "sha512-LvfVNDcWLw2AnIw5f2mWUgumW3I3N/WYGiWeimhQC1Ybt71n2FjlS9GJKeCnFeg1MKZHxzIFmpFnBXDI+sBeFg==",
+                       "version": "3.6.0",
+                       "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.6.0.tgz",
+                       "integrity": "sha512-BUrFS5TqSBdA0LwHop4OjPJwisqxGy6JsWVqV6qaFoe965qqtaKfDzHY5T2YA1gUL0ZeeQeA+4BBc1FJTcHiPw==",
                        "license": "Apache-2.0",
                        "optional": true,
                        "engines": {
                        }
                },
                "node_modules/esbuild-plugin-glsl": {
-                       "version": "1.2.3",
-                       "resolved": "https://registry.npmjs.org/esbuild-plugin-glsl/-/esbuild-plugin-glsl-1.2.3.tgz",
-                       "integrity": "sha512-PUM4rGm0ZBZI46Q9sF7XNZqEhVX1aa8Pxnh+kWPDMyeY7CPO4oEeC4wmKeTBmQekMuiRDB/tdAr0K79ZUbnmtQ==",
+                       "version": "1.4.0",
+                       "resolved": "https://registry.npmjs.org/esbuild-plugin-glsl/-/esbuild-plugin-glsl-1.4.0.tgz",
+                       "integrity": "sha512-8rWi34P9CKhNyzS3VSMw0F58NlIXf7u7n08c1Xtgce4dMR9uWgkuHXdZxTKsY/4YeLwVrGc22tA6SG4600v8Yw==",
                        "dev": true,
                        "license": "Zlib",
                        "engines": {
-                               "node": ">=18"
+                               "node": ">= 18"
                        },
                        "peerDependencies": {
                                "esbuild": "0.x.x"
index c0c0458d544b87d21537b39c36d3cf7e7fc4e20c..5a39e3253aeeabb0d1770233a880c6d560ee76f2 100644 (file)
                "url": "git+https://zoso.dev/nano-pow.git"
        },
        "scripts": {
-               "build": "rm -rf {dist,types} && tsc && node esbuild.mjs && npm run fix-copyright && cp -r src/bin src/types.d.ts dist",
-               "fix-copyright": "sed -i '/\\/\\/ src\\/shaders\\/compute\\.wgsl/a //! SPDX-FileCopyrightText: 2025 Chris Duncan <chris@zoso.dev>\\n//! SPDX-License-Identifier: GPL-3.0-or-later' dist/main.min.js"
+               "build": "rm -rf {dist,types} && tsc && node esbuild.mjs"
        },
        "devDependencies": {
                "@types/node": "^22.13.10",
-               "@webgpu/types": "^0.1.55",
+               "@webgpu/types": "^0.1.57",
                "esbuild": "^0.25.1",
-               "esbuild-plugin-glsl": "^1.2.3",
+               "esbuild-plugin-glsl": "^1.4.0",
                "typescript": "^5.8.2"
        },
        "type": "module",
similarity index 96%
rename from src/bin/cli.js
rename to src/bin/cli.ts
index 4f9b834d3ac78c96ad9da685a19a769c366a3933..7c314414bd17afad52fbab7bc7067cbae63bc337 100755 (executable)
@@ -6,9 +6,9 @@ import * as fs from 'node:fs/promises'
 import * as readline from 'node:readline/promises'
 import * as puppeteer from 'puppeteer'
 
-const hashes = []
+const hashes: string[] = []
 
-const stdinErrors = []
+const stdinErrors: string[] = []
 if (!process.stdin.isTTY) {
        const stdin = readline.createInterface({
                input: process.stdin
@@ -46,9 +46,9 @@ Full documentation: <https://www.npmjs.com/package/nano-pow>
        process.exit()
 }
 
-const inArgs = []
+const inArgs: string[] = []
 while (/^[0-9A-Fa-f]{64}$/.test(args[args.length - 1] ?? '')) {
-       inArgs.unshift(args.pop())
+       inArgs.unshift(args.pop() as string)
 }
 hashes.push(...inArgs)
 
@@ -115,13 +115,11 @@ if (hashes.length === 0) {
        const browser = await puppeteer.launch({
                headless: true,
                args: [
-                       '--no-sandbox',
                        '--headless=new',
                        '--use-angle=vulkan',
                        '--enable-features=Vulkan',
                        '--disable-vulkan-surface',
-                       '--enable-unsafe-webgpu',
-                       '--enable-vulkan'
+                       '--enable-unsafe-webgpu'
                ]
        })
        const page = await browser.newPage()
@@ -135,7 +133,7 @@ if (hashes.length === 0) {
                        if (output[1] === 'exit') {
                                if (isJson) {
                                        const results = await page.evaluate(() => {
-                                               return window.results
+                                               return (window as any).results
                                        })
                                        for (let i = 0; i < results.length; i++) {
                                                results[i] = {
similarity index 93%
rename from src/lib/gl/gl-downsample.ts
rename to src/lib/gl/gl-downsample.frag
index 9f15b47b6b237d9e2245b600826280d36a3e89c9..d9ab53326bcf037ff3c0332dbd1704f6a6ededed 100644 (file)
@@ -1,8 +1,8 @@
+#version 300 es
+#pragma vscode_glsllint_stage: frag
 //! SPDX-FileCopyrightText: 2025 Chris Duncan <chris@zoso.dev>
 //! SPDX-License-Identifier: GPL-3.0-or-later
 
-export const NanoPowGlDownsampleShader = `#version 300 es
-#pragma vscode_glsllint_stage: frag
 #ifdef GL_FRAGMENT_PRECISION_HIGH
 precision highp float;
 #else
@@ -33,4 +33,3 @@ void main() {
        pixel = texture(src, blockCoord + vec2(texel.x, texel.y));
        nonce = pixel.x == 0u ? nonce : pixel;
 }
-`
similarity index 94%
rename from src/lib/gl/gl-draw.ts
rename to src/lib/gl/gl-draw.frag
index ca113f51dd01ce6a20273d3213a89a0cae7b026b..bed53964b5c77255c93a73a3359156402c2540b7 100644 (file)
@@ -1,9 +1,9 @@
+#version 300 es
+#pragma vscode_glsllint_stage: frag
 //! SPDX-FileCopyrightText: 2025 Chris Duncan <chris@zoso.dev>
 //! SPDX-FileContributor: Ben Green <ben@latenightsketches.com>
 //! SPDX-License-Identifier: GPL-3.0-or-later AND MIT
 
-export const NanoPowGlDrawShader = `#version 300 es
-#pragma vscode_glsllint_stage: frag
 #ifdef GL_FRAGMENT_PRECISION_HIGH
 precision highp float;
 #else
@@ -14,11 +14,11 @@ out uvec4 nonce;
 
 // blockhash - Array of precalculated block hash components
 // threshold - 0xfffffff8 for send/change blocks, 0xfffffe00 for all else
-// search - Checks all pixels if true, else only checks 1 pixel to validate
+// validate - If true, checks only 1 pixel to validate, else checks all pixels to search
 layout(std140) uniform UBO {
        uint blockhash[8];
-       uint threshold;
-       bool search;
+       uvec2 threshold;
+       bool validate;
 };
 
 // Random work seed values
@@ -227,9 +227,9 @@ void main() {
        G(2u, 7u, 8u, 13u, m[11u], m[7u], 3u, 4u, 9u, 14u, m[5u], m[3u]);
 
        // Pixel data set from work seed values
-       // Finalize digest from high bits, low bits can be safely ignored
-       if ((BLAKE2B_INIT[0u].y ^ v[0u].y ^ v[8u].y) >= threshold && (search || uvec2(gl_FragCoord) == uvec2(0u))) {
-               nonce = uvec4(1u, m[0u].y, m[0u].x, (uint(gl_FragCoord.x) << 16u) | uint(gl_FragCoord.y));
+       uvec2 result = BLAKE2B_INIT[0u] ^ v[0u] ^ v[8u];
+       if ((validate && uvec2(gl_FragCoord) == uvec2(0u)) || (result.y > threshold.y || (result.y == threshold.y && result.x > threshold.x))) {
+               nonce = uvec4((BLAKE2B_INIT[0u] ^ v[0u] ^ v[8u]), m[0u]).yxwz;
        }
 
        // Valid nonce not found
@@ -237,4 +237,3 @@ void main() {
                discard;
        }
 }
-`
diff --git a/src/lib/gl/gl-vertex.ts b/src/lib/gl/gl-vertex.ts
deleted file mode 100644 (file)
index 9378c78..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-// SPDX-FileCopyrightText: 2025 Chris Duncan <chris@zoso.dev>
-// SPDX-FileContributor: Ben Green <ben@latenightsketches.com>
-// SPDX-License-Identifier: GPL-3.0-or-later AND MIT
-
-export const NanoPowGlVertexShader = `#version 300 es
-#pragma vscode_glsllint_stage: vert
-#ifdef GL_FRAGMENT_PRECISION_HIGH
-precision highp float;
-#else
-precision mediump float;
-#endif
-
-layout (location=0) in vec4 position;
-
-void main() {
-       gl_Position = position;
-}
-`
diff --git a/src/lib/gl/gl-vertex.vert b/src/lib/gl/gl-vertex.vert
new file mode 100644 (file)
index 0000000..9491409
--- /dev/null
@@ -0,0 +1,17 @@
+#version 300 es
+#pragma vscode_glsllint_stage: vert
+//! SPDX-FileCopyrightText: 2025 Chris Duncan <chris@zoso.dev>
+//! SPDX-FileContributor: Ben Green <ben@latenightsketches.com>
+//! SPDX-License-Identifier: GPL-3.0-or-later AND MIT
+
+#ifdef GL_FRAGMENT_PRECISION_HIGH
+precision highp float;
+#else
+precision mediump float;
+#endif
+
+layout (location=0) in vec4 position;
+
+void main() {
+       gl_Position = position;
+}
index 91d5e663c4e86c1e2ad85007a5f1703895297666..a1d45eefde84a87ddd9689bb9c6e30e2ae584092 100644 (file)
@@ -2,12 +2,18 @@
 //! SPDX-FileContributor: Ben Green <ben@latenightsketches.com>
 //! SPDX-License-Identifier: GPL-3.0-or-later AND MIT
 
-import { NanoPowGlDownsampleShader } from './gl-downsample.js'
-import { NanoPowGlDrawShader } from './gl-draw.js'
-import { NanoPowGlVertexShader } from './gl-vertex.js'
-import type { FBO, NanoPowOptions } from '../../types.d.ts'
-
+import { default as NanoPowGlDownsampleShader } from './gl-downsample.frag'
+import { default as NanoPowGlDrawShader } from './gl-draw.frag'
+import { default as NanoPowGlVertexShader } from './gl-vertex.vert'
+import type { FBO, NanoPowOptions, WorkGenerateRequest, WorkGenerateResponse, WorkValidateRequest, WorkValidateResponse } from '../../types.d.ts'
+
+/**
+* Nano proof-of-work using WebGL 2.0.
+*/
 export class NanoPowGl {
+       static #SEND: bigint = 0xfffffff800000000n
+       static #RECEIVE: bigint = 0xfffffe0000000000n
+
        static #busy: boolean = false
        static #debug: boolean = false
        static #raf: number = 0
@@ -15,7 +21,8 @@ export class NanoPowGl {
        static #cores: number = Math.max(1, Math.floor(navigator.hardwareConcurrency))
        static #WORKLOAD: number = 256 * this.#cores
        static #canvas: OffscreenCanvas
-       static get size () { return this.#gl?.drawingBufferWidth }
+       /** Drawing buffer size in pixels. */
+       static get size (): number { return (this.#gl?.drawingBufferWidth ?? 0) * (this.#gl?.drawingBufferHeight ?? 0) }
 
        static #gl: WebGL2RenderingContext | null
        static #drawProgram: WebGLProgram | null
@@ -34,12 +41,15 @@ export class NanoPowGl {
        static #query: WebGLQuery | null
        static #pixels: Uint32Array
 
-       /**Vertex Positions, 2 triangles */
+       /** Vertex positions for fullscreen quad. */
        static #positions: Float32Array = new Float32Array([
                -1, -1, 1, -1, 1, 1, -1, 1
        ])
 
-       /** Compile */
+       /**
+       * Constructs canvas, gets WebGL context, initializes buffers, and compiles
+       * shaders.
+       */
        static async init (): Promise<void> {
                if (this.#busy) return
                this.#busy = true
@@ -166,8 +176,8 @@ export class NanoPowGl {
 
                        /** Finalize configuration */
                        this.#query = this.#gl.createQuery()
-                       this.#pixels = new Uint32Array(this.#gl.drawingBufferWidth * this.#gl.drawingBufferHeight * 4)
-                       console.log(`NanoPow WebGL initialized at ${this.#gl.drawingBufferWidth}x${this.#gl.drawingBufferHeight}. Maximum nonces checked per frame: ${this.#gl.drawingBufferWidth * this.#gl.drawingBufferHeight}`)
+                       this.#pixels = new Uint32Array(this.size * 4)
+                       console.log(`NanoPow WebGL initialized at ${this.#gl.drawingBufferWidth}x${this.#gl.drawingBufferHeight}. Maximum nonces checked per frame: ${this.size}`)
                } catch (err) {
                        throw new Error('WebGL initialization failed.', { cause: err })
                } finally {
@@ -175,6 +185,10 @@ export class NanoPowGl {
                }
        }
 
+       /**
+       * On WebGL context loss, attempts to clear all program variables and then
+       * reinitialize them by calling `init()`.
+       */
        static reset (): void {
                cancelAnimationFrame(NanoPowGl.#raf)
                NanoPowGl.#gl?.deleteQuery(NanoPowGl.#query)
@@ -286,7 +300,7 @@ export class NanoPowGl {
        * @param {string} [workHex] - Original nonce if provided for a validation call
        * @returns Nonce as an 8-byte (16-char) hexadecimal string
        */
-       static #readResult (workHex?: string): string {
+       static #readResult (workHex?: string): { result: bigint, nonce: bigint } {
                if (this.#gl == null) throw new Error('WebGL 2 is required to read pixels')
                if (this.#drawFbo == null) throw new Error('Source FBO is required to downsample')
 
@@ -320,10 +334,16 @@ export class NanoPowGl {
                for (let i = 0; i < pixelCount; i += 4) {
                        if (this.#pixels[i] !== 0) {
                                if (this.#debug) console.log(`readResults (${performance.now() - start} ms)`)
-                               if (this.#debug) console.log(`Pixel: rgba(${this.#pixels[i]}, ${this.#pixels[i + 1]}, ${this.#pixels[i + 2]}, ${this.#pixels[i + 3].toString(16).padStart(8, '0')})`)
+                               if (this.#debug) console.log(`Pixel: rgba(${this.#pixels[i]}, ${this.#pixels[i + 1]}, ${this.#pixels[i + 2]}, ${this.#pixels[i + 3]})`)
                                /** Return the work value with the custom bits */
-                               const hex = `${this.#pixels[i + 1].toString(16).padStart(8, '0')}${this.#pixels[i + 2].toString(16).padStart(8, '0')}`
-                               if (workHex == null || workHex == hex) return hex
+                               const result = `${this.#pixels[i].toString(16).padStart(8, '0')}${this.#pixels[i + 1].toString(16).padStart(8, '0')}`
+                               const nonce = `${this.#pixels[i + 2].toString(16).padStart(8, '0')}${this.#pixels[i + 3].toString(16).padStart(8, '0')}`
+                               if (workHex == null || workHex == nonce) {
+                                       return {
+                                               result: BigInt(`0x${result}`),
+                                               nonce: BigInt(`0x${nonce}`)
+                                       }
+                               }
                        }
                }
                throw new Error('Query reported result but nonce value not found')
@@ -333,14 +353,28 @@ export class NanoPowGl {
        * Finds a nonce that satisfies the Nano proof-of-work requirements.
        *
        * @param {string} hash - Hexadecimal hash of previous block, or public key for new accounts
-       * @param {NanoPowOptions} options - Options used to configure search execution
+       * @param {NanoPowOptions} options - Used to configure search execution
        */
        static async search (hash: string, options?: NanoPowOptions): Promise<string> {
+               if (options?.threshold != null) {
+                       options.threshold = BigInt(`0x${options.threshold.toString(16) ?? '0'}00000000`)
+               }
+               const result = await this.work_generate(hash, options)
+               return result.work
+       }
+
+       /**
+       * Finds a nonce that satisfies the Nano proof-of-work requirements.
+       *
+       * @param {string} hash - Hexadecimal hash of previous block, or public key for new accounts
+       * @param {NanoPowOptions} options - Options used to configure search execution
+       */
+       static async work_generate (hash: string, options?: NanoPowOptions): Promise<WorkGenerateResponse> {
                if (this.#busy) {
                        console.log('NanoPowGl is busy. Retrying search...')
                        return new Promise(resolve => {
                                setTimeout(async (): Promise<void> => {
-                                       const result = this.search(hash, options)
+                                       const result = this.work_generate(hash, options)
                                        resolve(result)
                                }, 100)
                        })
@@ -349,8 +383,8 @@ export class NanoPowGl {
 
                /** Process user input */
                if (!/^[A-Fa-f0-9]{64}$/.test(hash)) throw new Error(`Invalid hash ${hash}`)
-               const threshold = (typeof options?.threshold !== 'number' || options.threshold < 0x0 || options.threshold > 0xffffffff)
-                       ? 0xfffffff8
+               const threshold = (typeof options?.threshold !== 'bigint' || options.threshold < 1n || options.threshold > 0xffffffffffffffffn)
+                       ? 0xfffffff800000000n
                        : options.threshold
                const effort = (typeof options?.effort !== 'number' || options.effort < 0x1 || options.effort > 0x20)
                        ? this.#cores
@@ -358,7 +392,7 @@ export class NanoPowGl {
                this.#debug = !!(options?.debug)
                if (this.#debug) console.log('NanoPowGl.search()')
                if (this.#debug) console.log('blockhash', hash)
-               if (this.#debug) console.log('search options', JSON.stringify(options))
+               if (this.#debug) console.log('search options', JSON.stringify(options, (k, v) => typeof v === 'bigint' ? v.toString(16) : v))
 
                /** Reset if user specified new level of effort */
                if (this.#WORKLOAD !== 256 * effort) {
@@ -382,8 +416,8 @@ export class NanoPowGl {
                        const uint32 = hash.slice(i, i + 8)
                        this.#uboView.setUint32(i * 2, parseInt(uint32, 16))
                }
-               this.#uboView.setUint32(128, threshold, true)
-               this.#uboView.setUint32(132, 1, true)
+               this.#uboView.setBigUint64(128, threshold, true)
+               this.#uboView.setUint32(136, 0, true)
                if (this.#debug) console.log('UBO', this.#uboView.buffer.slice(0))
                this.#gl.bindBuffer(this.#gl.UNIFORM_BUFFER, this.#uboBuffer)
                this.#gl.bufferSubData(this.#gl.UNIFORM_BUFFER, 0, this.#uboView)
@@ -392,25 +426,32 @@ export class NanoPowGl {
                /** Start drawing to calculate one nonce per pixel */
                let times = []
                let start = performance.now()
-               let nonce = null
+               let result = 0n
+               let nonce = 0n
+               let found = false
+
                if (this.#debug) console.groupCollapsed('Seeds (click to view)')
-               while (nonce == null) {
+               while (!found) {
                        start = performance.now()
                        const random0 = Math.floor(Math.random() * 0xffffffff)
                        const random1 = Math.floor(Math.random() * 0xffffffff)
                        this.#seed[0] = (BigInt(random0) << 32n) | BigInt(random1)
                        if (this.#debug) console.log('Seed', this.#seed)
                        this.#draw(this.#seed)
-                       const found = await this.#checkQueryResult()
+                       found = await this.#checkQueryResult()
                        times.push(performance.now() - start)
                        if (found) {
+                               ({ result, nonce } = this.#readResult())
                                if (this.#debug) console.groupEnd()
-                               nonce = this.#readResult()
                        }
                }
                this.#busy = false
                if (this.#debug) this.#logAverages(times)
-               return nonce
+               return {
+                       hash,
+                       work: nonce.toString(16).padStart(16, '0'),
+                       difficulty: result.toString(16).padStart(16, '0')
+               }
        }
 
        /**
@@ -421,11 +462,28 @@ export class NanoPowGl {
        * @param {NanoPowOptions} options - Options used to configure search execution
        */
        static async validate (work: string, hash: string, options?: NanoPowOptions): Promise<boolean> {
+               if (options?.threshold != null) {
+                       options.threshold = BigInt(`0x${options.threshold.toString(16) ?? '0'}00000000`)
+               }
+               const result = await this.work_validate(work, hash, options)
+               return (options?.threshold != null)
+                       ? result.valid === '1'
+                       : result.valid_all === '1'
+       }
+
+       /**
+       * Validates that a nonce satisfies Nano proof-of-work requirements.
+       *
+       * @param {string} work - Hexadecimal proof-of-work value to validate
+       * @param {string} hash - Hexadecimal hash of previous block, or public key for new accounts
+       * @param {NanoPowOptions} options - Options used to configure search execution
+       */
+       static async work_validate (work: string, hash: string, options?: NanoPowOptions): Promise<WorkValidateResponse> {
                if (this.#busy) {
                        console.log('NanoPowGl is busy. Retrying validate...')
                        return new Promise(resolve => {
                                setTimeout(async (): Promise<void> => {
-                                       const result = this.validate(work, hash, options)
+                                       const result = this.work_validate(work, hash, options)
                                        resolve(result)
                                }, 100)
                        })
@@ -435,13 +493,13 @@ export class NanoPowGl {
                /** Process user input */
                if (!/^[A-Fa-f0-9]{16}$/.test(work)) throw new Error(`Invalid work ${work}`)
                if (!/^[A-Fa-f0-9]{64}$/.test(hash)) throw new Error(`Invalid hash ${hash}`)
-               const threshold = (typeof options?.threshold !== 'number' || options.threshold < 0x0 || options.threshold > 0xffffffff)
-                       ? 0xfffffff8
+               const threshold = (typeof options?.threshold !== 'bigint' || options.threshold < 1n || options.threshold > 0xffffffffffffffffn)
+                       ? 0xfffffff800000000n
                        : options.threshold
                this.#debug = !!(options?.debug)
                if (this.#debug) console.log('NanoPowGl.validate()')
                if (this.#debug) console.log('blockhash', hash)
-               if (this.#debug) console.log('validate options', JSON.stringify(options))
+               if (this.#debug) console.log('validate options', JSON.stringify(options, (k, v) => typeof v === 'bigint' ? v.toString(16) : v))
 
                if (NanoPowGl.#gl == null) throw new Error('WebGL 2 is required')
                if (this.#gl == null) throw new Error('WebGL 2 is required')
@@ -458,28 +516,40 @@ export class NanoPowGl {
                        const uint32 = hash.slice(i, i + 8)
                        this.#uboView.setUint32(i * 2, parseInt(uint32, 16))
                }
-               this.#uboView.setUint32(128, threshold, true)
-               this.#uboView.setUint32(132, 0, true)
+               this.#uboView.setBigUint64(128, threshold, true)
+               this.#uboView.setUint32(136, 1, true)
                if (this.#debug) console.log('UBO', this.#uboView.buffer.slice(0))
                this.#gl.bindBuffer(this.#gl.UNIFORM_BUFFER, this.#uboBuffer)
                this.#gl.bufferSubData(this.#gl.UNIFORM_BUFFER, 0, this.#uboView)
                this.#gl.bindBuffer(this.#gl.UNIFORM_BUFFER, null)
 
                /** Start drawing to calculate one nonce per pixel */
-               let nonce = null
+               let result = 0n
+               let nonce = 0n
                this.#seed[0] = BigInt(`0x${work}`)
                if (this.#debug) console.log('Work', this.#seed)
                this.#draw(this.#seed)
                let found = await this.#checkQueryResult()
                if (found) {
                        try {
-                               nonce = this.#readResult(work)
+                               ({ result, nonce } = this.#readResult(work))
                        } catch (err) {
-                               found = false
+                               throw new Error('Error reading results', { cause: err })
                        }
+               } else {
+                       throw new Error('Failed to find nonce on canvas')
                }
                this.#busy = false
-               if (found && nonce !== work) throw new Error(`Nonce found but does not match work`)
-               return found
+               if (this.#debug) console.log('result', result, result.toString(16).padStart(16, '0'))
+               if (this.#debug) console.log('nonce', nonce, nonce.toString(16).padStart(16, '0'))
+               const response: WorkValidateResponse = {
+                       hash,
+                       work: nonce.toString(16).padStart(16, '0'),
+                       difficulty: result.toString(16).padStart(16, '0'),
+                       valid_all: (result >= this.#SEND) ? '1' : '0',
+                       valid_receive: (result >= this.#RECEIVE) ? '1' : '0',
+               }
+               if (options?.threshold != null) response.valid = (result >= threshold) ? '1' : '0'
+               return response
        }
 }
index 7615e28608f6d2137a9e5b6f22146f1dc417fa10..3b0c7efb898461b410808352bb6e441b06581594 100644 (file)
@@ -7,7 +7,7 @@
 struct UBO {
        blockhash: array<vec4<u32>, 2>,
        seed: vec2<u32>,
-       threshold: u32
+       threshold: vec2<u32>
 };
 @group(0) @binding(0) var<uniform> ubo: UBO;
 
@@ -118,7 +118,7 @@ fn search(@builtin(global_invocation_id) global_id: vec3<u32>, @builtin(local_in
        found = (local_id.x == 0u && atomicLoad(&work.found) != 0u);
        workgroupBarrier();
        if (found) { return; }
-       main(global_id);
+       main(global_id, false);
 }
 
 /**
@@ -127,7 +127,7 @@ fn search(@builtin(global_invocation_id) global_id: vec3<u32>, @builtin(local_in
 */
 @compute @workgroup_size(1)
 fn validate(@builtin(global_invocation_id) global_id: vec3<u32>) {
-       main(global_id);
+       main(global_id, true);
 }
 
 /**
@@ -140,7 +140,7 @@ fn validate(@builtin(global_invocation_id) global_id: vec3<u32>) {
 * implementation assigns each array element to its own variable to enhance
 * performance, but the variable name still contains the original index digit.
 */
-fn main(id: vec3<u32>) {
+fn main(id: vec3<u32>, validate: bool) {
        /**
        * Initialize (nonce||blockhash) concatenation
        */
@@ -1662,10 +1662,11 @@ fn main(id: vec3<u32>) {
        * Set nonce if it passes the threshold and no other thread has set it.
        * Only high bits are needed for comparison since threshold low bits are zero.
        */
-       if ((BLAKE2B_INIT[0u].y ^ v01.y ^ v89.y) >= ubo.threshold && atomicLoad(&work.found) == 0u) {
+       var result = BLAKE2B_INIT[0u] ^ v01.xy ^ v89.xy;
+       if (validate || ((result.y > ubo.threshold.y || (result.y == ubo.threshold.y && result.y >= ubo.threshold.y)) && atomicLoad(&work.found) == 0u)) {
                atomicStore(&work.found, 1u);
                work.nonce = m0;
-               work.result = (BLAKE2B_INIT[0u] ^ v01.xy ^ v89.xy);
+               work.result = result;
        }
        return;
 }
index 6f9759bc90d79fba504d8081c9b45e5f07f6d4ec..09f545e48d36cb60793357b2e2c748ab5cd82dfa 100644 (file)
@@ -3,11 +3,14 @@
 /// <reference types="@webgpu/types" />
 
 import { default as NanoPowGpuComputeShader } from './compute.wgsl'
-import type { NanoPowOptions } from '../../types.d.ts'
+import type { NanoPowOptions, WorkGenerateRequest, WorkGenerateResponse, WorkValidateRequest, WorkValidateResponse } from '../../types.d.ts'
+
 /**
 * Nano proof-of-work using WebGPU.
 */
 export class NanoPowGpu {
+       static #SEND: bigint = 0xfffffff800000000n
+       static #RECEIVE: bigint = 0xfffffe0000000000n
 
        // Initialize WebGPU
        static #busy: boolean = false
@@ -141,7 +144,7 @@ export class NanoPowGpu {
                console.table(averages)
        }
 
-       static async #dispatch (pipeline: GPUComputePipeline, seed: bigint, hash: string, threshold: number, passes: number): Promise<DataView> {
+       static async #dispatch (pipeline: GPUComputePipeline, seed: bigint, hash: string, threshold: bigint, passes: number): Promise<DataView> {
                if (this.#device == null) throw new Error(`WebGPU device failed to load.`)
                // Set up uniform buffer object
                // Note: u32 size is 4, but total alignment must be multiple of 16
@@ -151,7 +154,7 @@ export class NanoPowGpu {
                        this.#uboView.setBigUint64(i / 2, BigInt(`0x${u64}`))
                }
                this.#uboView.setBigUint64(32, seed, true)
-               this.#uboView.setUint32(40, threshold, true)
+               this.#uboView.setBigUint64(40, threshold, true)
                if (this.#debug) console.log('UBO', this.#uboView)
                this.#device.queue.writeBuffer(this.#uboBuffer, 0, this.#uboView)
 
@@ -217,19 +220,33 @@ export class NanoPowGpu {
        * @param {NanoPowOptions} options - Used to configure search execution
        */
        static async search (hash: string, options?: NanoPowOptions): Promise<string> {
+               if (options?.threshold != null) {
+                       options.threshold = BigInt(`0x${options.threshold.toString(16) ?? '0'}00000000`)
+               }
+               const result = await this.work_generate(hash, options)
+               return result.work
+       }
+
+       /**
+       * Finds a nonce that satisfies the Nano proof-of-work requirements.
+       *
+       * @param {string} hash - Hexadecimal hash of previous block, or public key for new accounts
+       * @param {NanoPowOptions} options - Used to configure search execution
+       */
+       static async work_generate (hash: string, options?: NanoPowOptions): Promise<WorkGenerateResponse> {
                if (!/^[A-Fa-f0-9]{64}$/.test(hash)) throw new TypeError(`Invalid hash ${hash}`)
                if (this.#busy) {
                        console.log('NanoPowGpu is busy. Retrying search...')
                        return new Promise(resolve => {
                                setTimeout(async (): Promise<void> => {
-                                       const result = this.search(hash, options)
+                                       const result = this.work_generate(hash, options)
                                        resolve(result)
                                }, 100)
                        })
                }
                this.#busy = true
-               const threshold = (typeof options?.threshold !== 'number' || options.threshold < 0x0 || options.threshold > 0xffffffff)
-                       ? 0xfffffff8
+               const threshold = (typeof options?.threshold !== 'bigint' || options.threshold < 1n || options.threshold > 0xffffffffffffffffn)
+                       ? 0xfffffff800000000n
                        : options.threshold
                const effort = (typeof options?.effort !== 'number' || options.effort < 0x1 || options.effort > 0x20)
                        ? 0x800
@@ -237,7 +254,7 @@ export class NanoPowGpu {
                this.#debug = !!(options?.debug)
                if (this.#debug) console.log('NanoPowGpu.search()')
                if (this.#debug) console.log('blockhash', hash)
-               if (this.#debug) console.log('search options', JSON.stringify(options))
+               if (this.#debug) console.log('search options', JSON.stringify(options, (k, v) => typeof v === 'bigint' ? v.toString(16) : v))
 
                // Ensure WebGPU is initialized before calculating
                let loads = 0
@@ -254,6 +271,7 @@ export class NanoPowGpu {
                let times = []
                let start = performance.now()
                let nonce = 0n
+               let result = 0n
                do {
                        start = performance.now()
                        const random0 = Math.floor(Math.random() * 0xffffffff)
@@ -263,13 +281,18 @@ export class NanoPowGpu {
                        const data = await this.#dispatch(this.#searchPipeline, seed, hash, threshold, effort)
                        const found = !!data.getUint32(0)
                        nonce = data.getBigUint64(8, true)
-                       if (this.#debug) console.log('result', data.getBigUint64(16).toString(16).padStart(16, '0'))
+                       result = data.getBigUint64(16, true)
                        this.#busy = !found
                        times.push(performance.now() - start)
                } while (this.#busy)
                if (this.#debug) this.#logAverages(times)
                if (this.#debug) console.log('nonce', nonce, nonce.toString(16).padStart(16, '0'))
-               return nonce.toString(16).padStart(16, '0')
+               if (this.#debug) console.log('result', result, result.toString(16).padStart(16, '0'))
+               return {
+                       hash,
+                       work: nonce.toString(16).padStart(16, '0'),
+                       difficulty: result.toString(16).padStart(16, '0')
+               }
        }
 
        /**
@@ -280,25 +303,42 @@ export class NanoPowGpu {
        * @param {NanoPowOptions} options - Options used to configure search execution
        */
        static async validate (work: string, hash: string, options?: NanoPowOptions): Promise<boolean> {
+               if (options?.threshold != null) {
+                       options.threshold = BigInt(`0x${options.threshold.toString(16) ?? '0'}00000000`)
+               }
+               const result = await this.work_validate(work, hash, options)
+               return (options?.threshold != null)
+                       ? result.valid === '1'
+                       : result.valid_all === '1'
+       }
+
+       /**
+       * Validates that a nonce satisfies Nano proof-of-work requirements.
+       *
+       * @param {string} work - Hexadecimal proof-of-work value to validate
+       * @param {string} hash - Hexadecimal hash of previous block, or public key for new accounts
+       * @param {NanoPowOptions} options - Options used to configure search execution
+       */
+       static async work_validate (work: string, hash: string, options?: NanoPowOptions): Promise<WorkValidateResponse> {
                if (!/^[A-Fa-f0-9]{16}$/.test(work)) throw new TypeError(`Invalid work ${work}`)
                if (!/^[A-Fa-f0-9]{64}$/.test(hash)) throw new TypeError(`Invalid hash ${hash}`)
                if (this.#busy) {
                        console.log('NanoPowGpu is busy. Retrying validate...')
                        return new Promise(resolve => {
                                setTimeout(async (): Promise<void> => {
-                                       const result = this.validate(work, hash, options)
+                                       const result = this.work_validate(work, hash, options)
                                        resolve(result)
                                }, 100)
                        })
                }
                this.#busy = true
-               const threshold = (typeof options?.threshold !== 'number' || options.threshold < 0x0 || options.threshold > 0xffffffff)
-                       ? 0xfffffff8
+               const threshold = (typeof options?.threshold !== 'bigint' || options.threshold < 1n || options.threshold > 0xffffffffffffffffn)
+                       ? 0xfffffff800000000n
                        : options.threshold
                this.#debug = !!(options?.debug)
                if (this.#debug) console.log('NanoPowGpu.validate()')
                if (this.#debug) console.log('blockhash', hash)
-               if (this.#debug) console.log('validate options', JSON.stringify(options))
+               if (this.#debug) console.log('validate options', JSON.stringify(options, (k, v) => typeof v === 'bigint' ? v.toString(16) : v))
 
                // Ensure WebGPU is initialized before calculating
                let loads = 0
@@ -312,15 +352,25 @@ export class NanoPowGpu {
                        throw new Error(`WebGPU device failed to load.`)
                }
 
+               let result = 0n
+               let nonce = 0n
+
                const seed = BigInt(`0x${work}`)
                if (this.#debug) console.log('work', work)
                const data = await this.#dispatch(this.#validatePipeline, seed, hash, threshold, 1)
-               const found = !!data.getUint32(0)
-               const nonce = data.getBigUint64(8, true)
-               if (this.#debug) console.log('result', data.getBigUint64(16).toString(16).padStart(16, '0'))
+               nonce = data.getBigUint64(8, true)
+               result = data.getBigUint64(16, true)
                this.#busy = false
                if (this.#debug) console.log('nonce', nonce, nonce.toString(16).padStart(16, '0'))
-               if (found && work !== nonce.toString(16).padStart(16, '0')) throw new Error(`Nonce (${nonce.toString(16).padStart(16, '0')}) found but does not match work (${work})`)
-               return found
+               if (this.#debug) console.log('result', result, result.toString(16).padStart(16, '0'))
+               const response: WorkValidateResponse = {
+                       hash,
+                       work: nonce.toString(16).padStart(16, '0'),
+                       difficulty: result.toString(16).padStart(16, '0'),
+                       valid_all: (result >= this.#SEND) ? '1' : '0',
+                       valid_receive: (result >= this.#RECEIVE) ? '1' : '0',
+               }
+               if (options?.threshold != null) response.valid = (result >= threshold) ? '1' : '0'
+               return response
        }
 }
index 0b9e45ce66c97b4b125999d7ae7aceb4fda4ddfa..414717a6c17337b83325f7e68c77cb18a3bc2fb6 100644 (file)
@@ -1,6 +1,6 @@
 //! SPDX-FileCopyrightText: 2025 Chris Duncan <chris@zoso.dev>
 //! SPDX-License-Identifier: GPL-3.0-or-later
 
-import { NanoPow, NanoPowGl, NanoPowGpu } from "./lib"
+import { NanoPow, NanoPowGl, NanoPowGpu } from './lib'
 export { NanoPow, NanoPowGl, NanoPowGpu }
 export default NanoPow
index 14cc26ec6499cada3f5defb4240b0d7dfd514008..fc5aa59f2c28bce58ff8eed93083d2d3bcbb12d3 100644 (file)
@@ -1,7 +1,67 @@
 // SPDX-FileCopyrightText: 2025 Chris Duncan <chris@zoso.dev>
 // SPDX-License-Identifier: GPL-3.0-or-later
 
-import "@webgpu/types"
+import '@webgpu/types'
+
+/**
+* Used by work server for inbound requests to `work_generate`.
+*
+* @param {string} action - Method to call
+* @param {string} hash - Block hash used to generate work
+* @param {string} [difficulty=FFFFFFF800000000] - Minimum threshold for a nonce to be valid
+*/
+type WorkGenerateRequest = {
+       action: 'work_generate'
+       hash: string
+       difficulty?: string
+}
+
+/**
+* Used by work server for outbound responses to `work_generate`.
+*
+* @param {string} hash - Block hash used to generate or validate work
+* @param {string} work - Valid proof-of-work nonce generated for input hash
+* @param {string} difficulty - BLAKE2b hash result which was compared to specified minimum threshold
+*/
+type WorkGenerateResponse = {
+       hash: string
+       work: string
+       difficulty: string
+}
+
+/**
+* Used by work server for inbound requests to `work_validate`.
+*
+* @param {string} action - Method to call
+* @param {string} hash - Block hash used to validate work
+* @param {string} work - Existing nonce to check against hash
+* @param {string} [difficulty=FFFFFFF800000000] - Minimum threshold for a nonce to be valid
+*/
+type WorkValidateRequest = {
+       action: 'work_validate'
+       hash: string
+       work: string
+       difficulty?: string
+}
+
+/**
+* Used by work server for outbound responses to `work_validate`.
+*
+* @param {string} hash - Hash from validate request
+* @param {string} work - Nonce from validate request
+* @param {string} difficulty - BLAKE2b hash result which is compared to specified minimum threshold
+* @param {string} [valid] - Excluded if optional difficulty was not included in the request. 1 for true if nonce is valid for requested difficulty, else 0 for false
+* @param {string} valid_all - 1 for true if nonce is valid for send blocks, else 0 for false
+* @param {string} valid_receive - 1 for true if nonce is valid for receive blocks, else 0 for false
+*/
+type WorkValidateResponse = {
+       hash: string
+       work: string
+       difficulty: string
+       valid?: '0' | '1'
+       valid_all: '0' | '1'
+       valid_receive: '0' | '1'
+}
 
 export declare const NanoPowGlDownsampleShader: string
 export declare const NanoPowGlDrawShader: string
@@ -36,24 +96,41 @@ export type FBO = {
 export type NanoPowOptions = {
        debug?: boolean
        effort?: number
-       threshold?: number
+       threshold?: bigint | number
 }
 
 /**
 * Nano proof-of-work using WebGL 2.0.
 */
 export declare class NanoPowGl {
-       /** Compile */
+       #private
+       /** Drawing buffer width in pixels. */
+       static get size (): number | undefined
+       /**
+       * Constructs canvas, gets WebGL context, initializes buffers, and compiles
+       * shaders.
+       */
        static init (): Promise<void>
+       /**
+       * On WebGL context loss, attempts to clear all program variables and then
+       * reinitialize them by calling `init()`.
+       */
        static reset (): void
        /**
        * Finds a nonce that satisfies the Nano proof-of-work requirements.
        *
        * @param {string} hash - Hexadecimal hash of previous block, or public key for new accounts
-       * @param {NanoPowOptions} options - Options used to configure search execution
+       * @param {NanoPowOptions} options - Used to configure search execution
        */
        static search (hash: string, options?: NanoPowOptions): Promise<string>
        /**
+       * Finds a nonce that satisfies the Nano proof-of-work requirements.
+       *
+       * @param {string} hash - Hexadecimal hash of previous block, or public key for new accounts
+       * @param {NanoPowOptions} options - Options used to configure search execution
+       */
+       static work_generate (hash: string, options?: NanoPowOptions): Promise<WorkGenerateResponse>
+       /**
        * Validates that a nonce satisfies Nano proof-of-work requirements.
        *
        * @param {string} work - Hexadecimal proof-of-work value to validate
@@ -61,12 +138,21 @@ export declare class NanoPowGl {
        * @param {NanoPowOptions} options - Options used to configure search execution
        */
        static validate (work: string, hash: string, options?: NanoPowOptions): Promise<boolean>
+       /**
+       * Validates that a nonce satisfies Nano proof-of-work requirements.
+       *
+       * @param {string} work - Hexadecimal proof-of-work value to validate
+       * @param {string} hash - Hexadecimal hash of previous block, or public key for new accounts
+       * @param {NanoPowOptions} options - Options used to configure search execution
+       */
+       static work_validate (work: string, hash: string, options?: NanoPowOptions): Promise<WorkValidateResponse>
 }
 
 /**
 * Nano proof-of-work using WebGPU.
 */
 export declare class NanoPowGpu {
+       #private
        static init (): Promise<void>
        static setup (): void
        static reset (): void
@@ -78,6 +164,13 @@ export declare class NanoPowGpu {
        */
        static search (hash: string, options?: NanoPowOptions): Promise<string>
        /**
+       * Finds a nonce that satisfies the Nano proof-of-work requirements.
+       *
+       * @param {string} hash - Hexadecimal hash of previous block, or public key for new accounts
+       * @param {NanoPowOptions} options - Used to configure search execution
+       */
+       static work_generate (hash: string, options?: NanoPowOptions): Promise<WorkGenerateResponse>
+       /**
        * Validates that a nonce satisfies Nano proof-of-work requirements.
        *
        * @param {string} work - Hexadecimal proof-of-work value to validate
@@ -85,4 +178,12 @@ export declare class NanoPowGpu {
        * @param {NanoPowOptions} options - Options used to configure search execution
        */
        static validate (work: string, hash: string, options?: NanoPowOptions): Promise<boolean>
+       /**
+       * Validates that a nonce satisfies Nano proof-of-work requirements.
+       *
+       * @param {string} work - Hexadecimal proof-of-work value to validate
+       * @param {string} hash - Hexadecimal hash of previous block, or public key for new accounts
+       * @param {NanoPowOptions} options - Options used to configure search execution
+       */
+       static work_validate (work: string, hash: string, options?: NanoPowOptions): Promise<WorkValidateResponse>
 }
index 55ad25590bb98215101fa074e030d0582ce7e613..2740258ef420de580e93e845cb3a0f2aaf42a072 100644 (file)
@@ -2,7 +2,10 @@
 8797585D56B8AEA3A62899C31FC088F9BE849BA8298A88E94F6E3112D4E55D01
 204076E3364D16A018754FF67D418AB2FBEB38799FF9A29A1D5F9E34F16BEEEA
 281E89AC73B1082B464B9C3C1168384F846D39F6DF25105F8B4A22915E999117
+7069D9CD1E85D6204301D254B0927F06ACC794C9EA5DF70EA5578458FB597090
 0000000000000000000000000000000000000000000000000000000000000000
 BA1E946BA3D778C2F30A83D44D2132CC6EEF010D8D06FF10A8ABD0100D8FB47E
 BF41D87DA3057FDC6050D2B00C06531F89F4AA6195D7C6C2EAAF15B6E703F8F6
 32721F4BD2AFB6F6A08D41CD0DF3C0D9C0B5294F68D0D12422F52B28F0800B5F
+39C57C28F904DFE4012288FFF64CE80C0F42601023A9C82108E8F7B2D186C150
+9DCD89E2B92FD59D7358C2C2E4C225DF94C88E187B27882F50FEFC3760D3994F
index a78e58b2dac23336e2df6350e110efe104fb190c..063eae5680de4496beba2e503c18dc43c8039767 100644 (file)
@@ -52,7 +52,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
                        }
                        const title = type === 'WebGPU'
                                ? `NanoPow (${type}) | Effort: ${effort} | Dispatch: ${(0x100 * effort) ** 2} | Threads: ${8 * 8 * (0x100 * effort) ** 2}`
-                               : `NanoPow (${type}) | Effort: ${effort} | Frame: ${NanoPowGl.size} | Pixels: ${NanoPowGl.size ** 2}`
+                               : `NanoPow (${type}) | Effort: ${effort} | Pixels: ${NanoPowGl.size}`
                        return {
                                [title]: {
                                        count: count,
@@ -78,6 +78,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
                        const expect = []
                        let result
 
+                       // PASS
                        result = await NP.validate('47c83266398728cf', '92BA74A7D6DC7557F3EDA95ADC6341D51AC777A0A6FF0688A5C492AB2B2CB40D', { debug: isDebug })
                        console.log(`validate() output for good nonce 1 is ${result === true ? 'correct' : 'incorrect'}`)
                        expect.push(result === true)
@@ -94,6 +95,11 @@ SPDX-License-Identifier: GPL-3.0-or-later
                        console.log(`validate() output for colliding nonce is ${result === true ? 'correct' : 'incorrect'}`)
                        expect.push(result === true)
 
+                       result = await NP.validate('6866c1ac3831a891', '7069D9CD1E85D6204301D254B0927F06ACC794C9EA5DF70EA5578458FB597090', { threshold: 0xfffffe00, debug: isDebug })
+                       console.log(`validate() output for good receive threshold nonce is ${result === true ? 'correct' : 'incorrect'}`)
+                       expect.push(result === true)
+
+                       // XFAIL
                        result = await NP.validate('0000000000000000', '0000000000000000000000000000000000000000000000000000000000000000', { debug: isDebug })
                        console.log(`validate() output for bad nonce 1 is ${result === false ? 'correct' : 'incorrect'}`)
                        expect.push(result === false)
@@ -110,6 +116,13 @@ SPDX-License-Identifier: GPL-3.0-or-later
                        console.log(`validate() output for slightly wrong nonce is ${result === false ? 'correct' : 'incorrect'}`)
                        expect.push(result === false)
 
+                       result = await NP.validate('7d903b18d03f9820', '39C57C28F904DFE4012288FFF64CE80C0F42601023A9C82108E8F7B2D186C150', { threshold: 0xfffffe00, debug: isDebug })
+                       console.log(`validate() output for bad receive threshold nonce is ${result === false ? 'correct' : 'incorrect'}`)
+                       expect.push(result === false)
+
+                       result = await NP.validate('e45835c3b291c3d1', '9DCD89E2B92FD59D7358C2C2E4C225DF94C88E187B27882F50FEFC3760D3994F', { threshold: 0xffffffff, debug: isDebug })
+                       console.log(`validate() output for send threshold nonce that does not meet custom threshold is ${result === false ? 'correct' : 'incorrect'}`)
+                       expect.push(result === false)
 
                        try {
                                if (!expect.every(result => result)) throw new Error(`Validation is not working`)