nano-pow 4.0.7 → 4.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin/cli.js CHANGED
@@ -1,161 +1,136 @@
1
1
  #!/usr/bin/env node
2
2
  //! SPDX-FileCopyrightText: 2025 Chris Duncan <chris@zoso.dev>
3
3
  //! SPDX-License-Identifier: GPL-3.0-or-later
4
- import * as crypto from "node:crypto";
5
- import * as fs from "node:fs/promises";
6
4
  import * as readline from "node:readline/promises";
7
- import * as puppeteer from "puppeteer";
8
- import { cliHelp } from "../../docs/index.js";
5
+ process.title = "NanoPow CLI";
6
+ process.env.NANO_POW_DEBUG = "";
7
+ process.env.NANO_POW_EFFORT = "";
8
+ process.env.NANO_POW_PORT = "3000";
9
+ function log(...args2) {
10
+ if (process.env.NANO_POW_DEBUG) console.log(new Date(Date.now()).toLocaleString(), "NanoPow", args2);
11
+ }
9
12
  const hashes = [];
10
13
  const stdinErrors = [];
11
14
  if (!process.stdin.isTTY) {
12
15
  const stdin = readline.createInterface({
13
16
  input: process.stdin
14
17
  });
18
+ let i = 0;
15
19
  for await (const line of stdin) {
20
+ i++;
16
21
  if (/^[0-9A-Fa-f]{64}$/.test(line)) {
17
22
  hashes.push(line);
18
23
  } else {
19
- stdinErrors.push(`Skipping invalid stdin input: ${line}`);
24
+ stdinErrors.push(`Skipping invalid stdin input line ${i}`);
20
25
  }
21
26
  }
22
27
  }
23
28
  const args = process.argv.slice(2);
24
29
  if (hashes.length === 0 && args.length === 0 || args.some((v) => v === "--help" || v === "-h")) {
25
- console.log(cliHelp);
26
- process.exit();
30
+ console.log(
31
+ `Usage: nano-pow [OPTION]... BLOCKHASH...
32
+ Generate work for BLOCKHASH, or multiple work values for BLOCKHASH(es)
33
+ BLOCKHASH is a 64-character hexadecimal string. Multiple blockhashes must be separated by whitespace or line breaks.
34
+ Prints the result as a Javascript object to standard output as soon as it is calculated.
35
+ If using --batch, results are printed only after all BLOCKHASH(es) have be processed.
36
+ If using --validate, results will also include validity properties.
37
+
38
+ -h, --help show this dialog
39
+ --debug enable additional logging output
40
+
41
+ -b, --batch process all data before returning final results as array
42
+ -d, --difficulty=<value> override the minimum difficulty value
43
+ -e, --effort=<value> increase demand on GPU processing
44
+ -v, --validate=<value> check an existing work value instead of searching for one
45
+
46
+ If validating a nonce, it must be a 16-character hexadecimal value.
47
+ Effort must be a decimal number between 1-32.
48
+ Difficulty must be a hexadecimal string between 1-FFFFFFFFFFFFFFFF.
49
+
50
+ Report bugs: <bug-nano-pow@zoso.dev>
51
+ Full documentation: <https://www.npmjs.com/package/nano-pow>
52
+ `
53
+ );
54
+ process.exit(0);
27
55
  }
28
56
  const inArgs = [];
29
57
  while (/^[0-9A-Fa-f]{64}$/.test(args[args.length - 1] ?? "")) {
30
58
  inArgs.unshift(args.pop());
31
59
  }
32
60
  hashes.push(...inArgs);
33
- let fn = "work_generate";
34
- let work = "";
35
- let isJson = false;
36
- const options = {};
61
+ if (hashes.length === 0) {
62
+ console.error("Invalid block hash input");
63
+ process.exit(1);
64
+ }
65
+ let isBatch = false;
66
+ const body = {
67
+ action: "work_generate"
68
+ };
37
69
  for (let i = 0; i < args.length; i++) {
38
70
  switch (args[i]) {
39
71
  case "--validate":
40
72
  case "-v": {
41
73
  if (args[i + 1] == null) throw new Error("Missing argument for work validation");
42
74
  if (!/^[0-9A-Fa-f]{16}$/.test(args[i + 1])) throw new Error("Invalid work to validate");
43
- fn = "work_validate";
44
- work = `'${args[i + 1]}', `;
75
+ body.action = "work_validate";
76
+ body.work = args[i + 1];
45
77
  break;
46
78
  }
47
- case "--threshold":
48
- case "-t": {
49
- if (args[i + 1] == null) throw new Error("Missing argument for threshold");
50
- if (!/^[0-9A-Fa-f]{0,8}$/.test(args[i + 1])) throw new Error("Invalid threshold");
51
- options["threshold"] = parseInt(args[i + 1], 16);
79
+ case "--difficulty":
80
+ case "-d": {
81
+ if (args[i + 1] == null) throw new Error("Missing argument for difficulty");
82
+ if (!/^[0-9A-Fa-f][0-9A-Fa-f]{0,15}$/.test(args[i + 1])) throw new Error("Invalid difficulty");
83
+ body.difficulty = args[i + 1];
52
84
  break;
53
85
  }
54
86
  case "--effort":
55
87
  case "-e": {
56
88
  if (args[i + 1] == null) throw new Error("Missing argument for effort");
57
89
  if (!/^[0-9]{0,2}$/.test(args[i + 1])) throw new Error("Invalid effort");
58
- options["effort"] = parseInt(args[i + 1], 10);
90
+ process.env.NANO_POW_EFFORT = args[i + 1];
59
91
  break;
60
92
  }
61
- case "--debug":
62
- case "-d": {
63
- options["debug"] = true;
93
+ case "--debug": {
94
+ process.env.NANO_POW_DEBUG = "true";
64
95
  break;
65
96
  }
66
- case "--json":
67
- case "-j": {
68
- isJson = true;
97
+ case "--batch":
98
+ case "-b": {
99
+ isBatch = true;
69
100
  break;
70
101
  }
71
102
  }
72
103
  }
73
- if (options["debug"]) {
74
- console.log(`NanoPowCli.${fn}()`);
75
- console.log(`${fn} options`, JSON.stringify(options));
76
- for (const stdinErr of stdinErrors) {
77
- console.warn(stdinErr);
78
- }
79
- }
80
- if (hashes.length === 0) {
81
- console.error("Invalid block hash input");
82
- process.exit(1);
104
+ log("CLI args:", ...args);
105
+ for (const stdinErr of stdinErrors) {
106
+ log(stdinErr);
83
107
  }
84
- (async () => {
85
- const NanoPow = await fs.readFile(new URL("../main.min.js", import.meta.url), "utf-8");
86
- const browser = await puppeteer.launch({
87
- headless: true,
88
- args: [
89
- "--headless=new",
90
- "--use-angle=vulkan",
91
- "--enable-features=Vulkan",
92
- "--disable-vulkan-surface",
93
- "--enable-unsafe-webgpu"
94
- ]
95
- });
96
- const page = await browser.newPage();
97
- const path = new URL(import.meta.url).pathname;
98
- const dir = path.slice(0, path.lastIndexOf("/"));
99
- await fs.writeFile(`${dir}/cli.html`, "");
100
- await page.goto(import.meta.resolve("./cli.html"));
101
- await page.waitForFunction(async () => {
102
- return await navigator.gpu.requestAdapter();
103
- });
104
- const inject = `
105
- ${NanoPow}
106
- window.results = []
107
- const hashes = ["${hashes.join('","')}"]
108
- for (const hash of hashes) {
109
- try {
110
- const result = await NanoPow.${fn}(${work}hash, ${JSON.stringify(options)})
111
- window.results.push(result)
112
- console.log(\`cli \${JSON.stringify(result, null, 4)}\`)
113
- } catch (err) {
114
- console.error(\`cli \${err}\`)
115
- }
116
- }
117
- console.log('cli exit')
118
- `;
119
- const hash = await crypto.subtle.digest("SHA-256", Buffer.from(inject, "utf-8"));
120
- const src = `sha256-${Buffer.from(hash).toString("base64")}`;
121
- let start = performance.now();
122
- page.on("console", async (msg) => {
123
- const output = msg.text().split(/^cli /);
124
- if (output[0] === "") {
125
- if (output[1] === "exit") {
126
- if (isJson) {
127
- const results = await page.evaluate(() => {
128
- return window.results;
129
- });
130
- console.log(JSON.stringify(results, null, 4));
131
- }
132
- const end = performance.now();
133
- if (options["debug"]) console.log(end - start, "ms total |", (end - start) / hashes.length, "ms avg");
134
- await browser.close();
135
- } else if (!isJson) {
136
- try {
137
- console.log(JSON.parse(output[1]));
138
- } catch (err) {
139
- console.log(output[1]);
140
- }
141
- }
142
- } else if (options["debug"]) {
143
- try {
144
- console.log(JSON.parse(msg.text()));
145
- } catch (err) {
146
- console.log(msg.text());
147
- }
108
+ log("Starting NanoPow CLI");
109
+ await import("./server.js");
110
+ const results = [];
111
+ const start = performance.now();
112
+ const aborter = new AbortController();
113
+ for (const hash of hashes) {
114
+ try {
115
+ body.hash = hash;
116
+ const kill = setTimeout(aborter.abort, 5e3);
117
+ const response = await fetch(`http://localhost:${process.env.NANO_POW_PORT}`, {
118
+ method: "POST",
119
+ body: JSON.stringify(body),
120
+ signal: aborter.signal
121
+ });
122
+ clearTimeout(kill);
123
+ const result = await response.json();
124
+ if (isBatch) {
125
+ results.push(result);
126
+ } else {
127
+ console.log(result);
148
128
  }
149
- });
150
- start = performance.now();
151
- await page.setContent(`
152
- <!DOCTYPE html>
153
- <head>
154
- <meta http-equiv="Content-Security-Policy" content="default-src 'none'; base-uri 'none'; form-action 'none'; script-src '${src}';">
155
- <script type="module">${inject}</script>
156
- </head>
157
- </html>
158
- `);
159
- await fs.unlink(`${dir}/cli.html`);
160
- if (options["debug"]) console.log("Puppeteer initialized");
161
- })();
129
+ } catch (err) {
130
+ log(err);
131
+ }
132
+ }
133
+ const end = performance.now();
134
+ if (isBatch) console.log(results);
135
+ log(end - start, "ms total |", (end - start) / hashes.length, "ms avg");
136
+ process.exit(0);
@@ -1,176 +1,166 @@
1
1
  #!/usr/bin/env node
2
2
  //! SPDX-FileCopyrightText: 2025 Chris Duncan <chris@zoso.dev>
3
3
  //! SPDX-License-Identifier: GPL-3.0-or-later
4
- import * as crypto from "node:crypto";
5
- import * as dns from "node:dns/promises";
6
- import * as fs from "node:fs/promises";
4
+ import { launch } from "puppeteer";
5
+ import { subtle } from "node:crypto";
6
+ import { lookup } from "node:dns/promises";
7
+ import { readFile, unlink, writeFile } from "node:fs/promises";
7
8
  import * as http from "node:http";
8
- import * as os from "node:os";
9
- import * as puppeteer from "puppeteer";
10
- import { serverHelp } from "../../docs/index.js";
11
- const PORT = process.env.PORT || 3e3;
9
+ import { hostname } from "node:os";
10
+ import { join } from "node:path";
11
+ process.title = "NanoPow Server";
12
+ const MAX_REQUEST_SIZE = 1024;
13
+ const MAX_BODY_SIZE = 158;
14
+ const DEBUG = !!(process.env.NANO_POW_DEBUG || false);
12
15
  const EFFORT = +(process.env.NANO_POW_EFFORT || 8);
13
- function log(...args) {
14
- console.log(new Date(Date.now()).toLocaleString(), "NanoPow", args);
15
- }
16
- log("Starting server");
17
- const NanoPow = await fs.readFile(new URL("../main.min.js", import.meta.url), "utf-8");
16
+ const PORT = +(process.env.NANO_POW_PORT || 3e3);
18
17
  let browser;
19
18
  let page;
20
- async function work_generate(res, json) {
21
- if (!/^[0-9A-Fa-f]{64}$/.test(json.hash ?? "")) {
22
- res.writeHead(400, { "Content-Type": "application/json" });
23
- res.end(JSON.stringify({ error: "Invalid hash. Must be a 64-character hex string." }));
24
- return;
25
- }
26
- if (json.difficulty && !/^[1-9A-Fa-f][0-9A-Fa-f]{0,15}$/.test(json.difficulty)) {
27
- res.writeHead(400, { "Content-Type": "application/json" });
28
- res.end(JSON.stringify({ error: "Invalid difficulty. Must be a hexadecimal string between 1-FFFFFFFFFFFFFFFF." }));
29
- return;
30
- }
19
+ function log(...args) {
20
+ if (DEBUG) console.log(new Date(Date.now()).toLocaleString(), "NanoPow", args);
21
+ }
22
+ async function respond(res, data) {
23
+ let statusCode = 500;
24
+ let headers = { "Content-Type": "application/json" };
25
+ let response = "work_validate failed";
31
26
  try {
32
- const result = await page.evaluate(async (json2, options) => {
33
- if (json2.difficulty) options.threshold = BigInt(`0x${json2.difficulty}`);
34
- return await window.NanoPow.work_generate(json2.hash, options);
35
- }, json, { debug: true, effort: EFFORT });
36
- res.writeHead(200, { "Content-Type": "application/json" });
37
- res.end(JSON.stringify(result));
27
+ const datastring = Buffer.concat(data).toString().replace(/\s+/g, "");
28
+ if (Buffer.byteLength(datastring) > MAX_BODY_SIZE) {
29
+ throw new Error("Invalid data.");
30
+ }
31
+ const { action, hash: hash2, work, difficulty } = JSON.parse(datastring);
32
+ if (action !== "work_generate" && action !== "work_validate") {
33
+ throw new Error("Invalid action. Must be work_generate or work_validate.");
34
+ }
35
+ if (!/^[0-9A-Fa-f]{64}$/.test(hash2 ?? "")) {
36
+ throw new Error("Invalid hash. Must be a 64-character hex string.");
37
+ }
38
+ if (difficulty && !/^[1-9A-Fa-f][0-9A-Fa-f]{0,15}$/.test(difficulty)) {
39
+ throw new Error("Invalid difficulty. Must be a hexadecimal string between 1-FFFFFFFFFFFFFFFF.");
40
+ }
41
+ if (action === "work_validate" && !/^[0-9A-Fa-f]{16}$/.test(work ?? "")) {
42
+ throw new Error("Invalid work. Must be a 16-character hex string.");
43
+ }
44
+ const options = {
45
+ debug: DEBUG,
46
+ effort: EFFORT,
47
+ threshold: difficulty
48
+ };
49
+ const args = [];
50
+ if (work) args.push(work);
51
+ args.push(hash2);
52
+ args.push(options);
53
+ response = JSON.stringify(await page.evaluate(async (action2, args2) => {
54
+ if (window.NanoPow == null) throw new Error("NanoPow not found");
55
+ return await window.NanoPow[action2](...args2);
56
+ }, action, args));
57
+ statusCode = 200;
38
58
  } catch (err) {
39
- log("work_generate error:", err);
40
- res.writeHead(500, { "Content-Type": "application/json" });
41
- res.end(JSON.stringify({ error: "work_generate failed" }));
59
+ log(err);
60
+ statusCode = 400;
61
+ } finally {
62
+ res.writeHead(statusCode, headers).end(response);
42
63
  }
43
64
  }
44
- async function work_validate(res, json) {
45
- if (!/^[0-9A-Fa-f]{64}$/.test(json.hash ?? "")) {
46
- res.writeHead(400, { "Content-Type": "application/json" });
47
- res.end(JSON.stringify({ error: "Invalid hash. Must be a 64-character hex string." }));
48
- return;
49
- }
50
- if (json.difficulty && !/^[1-9A-Fa-f][0-9A-Fa-f]{0,15}$/.test(json.difficulty)) {
51
- res.writeHead(400, { "Content-Type": "application/json" });
52
- res.end(JSON.stringify({ error: "Invalid difficulty. Must be a hexadecimal string between 1-FFFFFFFFFFFFFFFF." }));
53
- return;
54
- }
55
- if (!/^[0-9A-Fa-f]{16}$/.test(json.work ?? "")) {
56
- res.writeHead(400, { "Content-Type": "application/json" });
57
- res.end(JSON.stringify({ error: "Invalid work. Must be a 16-character hex string." }));
58
- return;
65
+ const server = http.createServer((req, res) => {
66
+ let data = [];
67
+ let reqSize = 0;
68
+ if (req.method === "POST") {
69
+ req.on("data", (chunk) => {
70
+ reqSize += chunk.byteLength;
71
+ if (reqSize > MAX_REQUEST_SIZE) {
72
+ res.writeHead(413, { "Content-Type": "text/plain" });
73
+ res.end("Content Too Large");
74
+ req.socket.destroy();
75
+ return;
76
+ }
77
+ data.push(chunk);
78
+ });
79
+ req.on("end", async () => {
80
+ if (!req.socket.destroyed) {
81
+ await respond(res, data);
82
+ }
83
+ });
84
+ } else {
85
+ res.writeHead(200, { "Content-Type": "text/plain" });
86
+ res.end(
87
+ `Usage: Send POST request to server URL to generate or validate Nano proof-of-work
88
+
89
+ Generate work for a BLOCKHASH with an optional DIFFICULTY:
90
+ curl -d '{ action: "work_generate", hash: BLOCKHASH, difficulty?: DIFFICULTY }'
91
+
92
+ Validate WORK previously calculated for a BLOCKHASH with an optional DIFFICULTY:
93
+ curl -d '{ action: "work_validate", work: WORK, hash: BLOCKHASH, difficulty?: DIFFICULTY }'
94
+
95
+ BLOCKHASH is a 64-character hexadecimal string.
96
+ WORK is 16-character hexadecimal string.
97
+ DIFFICULTY is a 16-character hexadecimal string (default: FFFFFFF800000000)
98
+
99
+ Report bugs: <bug-nano-pow@zoso.dev>
100
+ Full documentation: <https://www.npmjs.com/package/nano-pow>
101
+ `
102
+ );
59
103
  }
104
+ });
105
+ server.on("error", (e) => {
106
+ log("Server error", e);
60
107
  try {
61
- const result = await page.evaluate(async (json2, options) => {
62
- if (json2.difficulty) options.threshold = BigInt(`0x${json2.difficulty}`);
63
- return await window.NanoPow.work_validate(json2.work, json2.hash, options);
64
- }, json, { debug: true, effort: EFFORT });
65
- res.writeHead(200, { "Content-Type": "application/json" });
66
- res.end(JSON.stringify(result));
108
+ shutdown();
67
109
  } catch (err) {
68
- log("work_validate error:", err);
69
- res.writeHead(500, { "Content-Type": "application/json" });
70
- res.end(JSON.stringify({ error: "work_validate failed" }));
110
+ log("Failed to shut down", err);
111
+ process.exit(1);
71
112
  }
72
- }
73
- (async () => {
74
- browser = await puppeteer.launch({
75
- headless: true,
76
- args: [
77
- "--headless=new",
78
- "--use-angle=vulkan",
79
- "--enable-features=Vulkan",
80
- "--disable-vulkan-surface",
81
- "--enable-unsafe-webgpu"
82
- ]
83
- });
84
- page = await browser.newPage();
85
- page.on("console", (msg) => {
86
- log(msg.text());
87
- });
88
- const path = new URL(import.meta.url).pathname;
89
- const dir = path.slice(0, path.lastIndexOf("/"));
90
- await fs.writeFile(`${dir}/server.html`, "");
91
- await page.goto(import.meta.resolve("./server.html"));
92
- await page.waitForFunction(async () => {
93
- return await navigator.gpu.requestAdapter();
94
- });
95
- const inject = `${NanoPow};window.NanoPow=NanoPow;`;
96
- const hash = await crypto.subtle.digest("SHA-256", Buffer.from(inject, "utf-8"));
97
- const src = `sha256-${Buffer.from(hash).toString("base64")}`;
98
- await page.setContent(`
99
- <!DOCTYPE html>
100
- <head>
101
- <meta http-equiv="Content-Security-Policy" content="default-src 'none'; base-uri 'none'; form-action 'none'; script-src '${src}';">
102
- <script type="module">${inject}</script>
103
- </head>
104
- </html>
105
- `);
106
- await fs.unlink(`${dir}/server.html`);
107
- log("Puppeteer initialized");
108
- const server = http.createServer(async (req, res) => {
109
- let data = [];
110
- if (req.method === "POST") {
111
- req.on("data", (chunk) => {
112
- data.push(chunk);
113
- });
114
- req.on("end", async () => {
115
- let json;
116
- try {
117
- json = JSON.parse(Buffer.concat(data).toString());
118
- } catch (err) {
119
- log("JSON.parse error:", err);
120
- log("Failed JSON:", json);
121
- res.writeHead(400, { "Content-Type": "application/json" });
122
- res.end(JSON.stringify({ error: "Invalid data." }));
123
- return;
124
- }
125
- switch (json.action) {
126
- case "work_generate": {
127
- await work_generate(res, json);
128
- break;
129
- }
130
- case "work_validate": {
131
- await work_validate(res, json);
132
- break;
133
- }
134
- default: {
135
- res.writeHead(400, { "Content-Type": "application/json" });
136
- res.end(JSON.stringify({ error: `Invalid data.` }));
137
- return;
138
- }
139
- }
140
- });
141
- } else {
142
- res.writeHead(200, { "Content-Type": "text/plain" });
143
- res.end(serverHelp);
144
- }
113
+ });
114
+ function shutdown() {
115
+ log("Shutdown signal received");
116
+ const kill = setTimeout(() => {
117
+ log("Server unresponsive, forcefully stopped");
118
+ process.exit(1);
119
+ }, 1e4);
120
+ server.close(() => {
121
+ clearTimeout(kill);
122
+ log("Server stopped");
123
+ process.exit(0);
145
124
  });
146
- server.on("error", (e) => {
147
- log("Server error", e);
148
- try {
149
- shutdown();
150
- } catch (err) {
151
- log("Failed to shut down", err);
152
- process.exit(1);
153
- }
154
- });
155
- server.listen(PORT, async () => {
156
- process.title = "NanoPow Server";
157
- const ip = await dns.lookup(os.hostname(), { family: 4 });
158
- log(`Server process ${process.pid} running at ${ip.address}:${PORT}/`);
159
- });
160
- function shutdown() {
161
- log("Shutdown signal received");
162
- const kill = setTimeout(() => {
163
- log("Server unresponsive, forcefully stopped");
164
- process.exit(1);
165
- }, 1e4);
166
- server.close(async () => {
167
- await page?.close();
168
- await browser?.close();
169
- clearTimeout(kill);
170
- log("Server stopped");
171
- process.exit(0);
172
- });
173
- }
174
- process.on("SIGINT", shutdown);
175
- process.on("SIGTERM", shutdown);
176
- })();
125
+ }
126
+ process.on("SIGINT", shutdown);
127
+ process.on("SIGTERM", shutdown);
128
+ log("Starting NanoPow work server");
129
+ const NanoPow = await readFile(new URL("../main.min.js", import.meta.url), "utf-8");
130
+ browser = await launch({
131
+ headless: true,
132
+ args: [
133
+ "--headless=new",
134
+ "--use-angle=vulkan",
135
+ "--enable-features=Vulkan",
136
+ "--disable-vulkan-surface",
137
+ "--enable-unsafe-webgpu"
138
+ ]
139
+ });
140
+ page = await browser.newPage();
141
+ page.on("console", (msg) => log(msg.text()));
142
+ const path = new URL(import.meta.url).pathname;
143
+ const dir = path.slice(0, path.lastIndexOf("/"));
144
+ const filename = join(dir, `${process.pid}.html`);
145
+ await writeFile(filename, "");
146
+ await page.goto(import.meta.resolve(filename));
147
+ await page.waitForFunction(async () => {
148
+ return await navigator["gpu"].requestAdapter();
149
+ });
150
+ const src = `${NanoPow};window.NanoPow=NanoPow;`;
151
+ const hash = await subtle.digest("SHA-256", Buffer.from(src));
152
+ const enc = `sha256-${Buffer.from(hash).toString("base64")}`;
153
+ await page.setContent(`
154
+ <!DOCTYPE html>
155
+ <head>
156
+ <meta http-equiv="Content-Security-Policy" content="default-src 'none'; base-uri 'none'; form-action 'none'; script-src '${enc}';">
157
+ <script type="module">${src}</script>
158
+ </head>
159
+ </html>
160
+ `);
161
+ await unlink(filename);
162
+ log("Puppeteer initialized");
163
+ server.listen(PORT, async () => {
164
+ const ip = await lookup(hostname(), { family: 4 });
165
+ log(`Server process ${process.pid} running at ${ip.address}:${PORT}/`);
166
+ });
package/dist/main.min.js CHANGED
@@ -51,10 +51,6 @@ var NanoPowGl = class _NanoPowGl {
51
51
  static #cores = Math.max(1, Math.floor(navigator.hardwareConcurrency));
52
52
  static #WORKLOAD = 256 * this.#cores;
53
53
  static #canvas;
54
- /** Drawing buffer size in pixels. */
55
- static get size() {
56
- return (this.#gl?.drawingBufferWidth ?? 0) * (this.#gl?.drawingBufferHeight ?? 0);
57
- }
58
54
  static #gl;
59
55
  static #drawProgram;
60
56
  static #downsampleProgram;
@@ -187,14 +183,14 @@ var NanoPowGl = class _NanoPowGl {
187
183
  this.#gl.uniformBlockBinding(this.#drawProgram, this.#gl.getUniformBlockIndex(this.#drawProgram, "WORK"), 1);
188
184
  this.#gl.bindBuffer(this.#gl.UNIFORM_BUFFER, null);
189
185
  this.#query = this.#gl.createQuery();
190
- this.#pixels = new Uint32Array(this.size * 4);
186
+ this.#pixels = new Uint32Array(this.#gl.drawingBufferWidth * this.#gl.drawingBufferHeight * 4);
191
187
  } catch (err) {
192
188
  throw new Error("WebGL initialization failed.", { cause: err });
193
189
  } finally {
194
190
  this.#busy = false;
195
191
  }
196
192
  this.#isInitialized = true;
197
- console.log(`NanoPow WebGL initialized at ${this.#gl.drawingBufferWidth}x${this.#gl.drawingBufferHeight}. Maximum nonces checked per frame: ${this.size}`);
193
+ console.log(`NanoPow WebGL initialized. Maximum nonces checked per frame: ${this.#gl.drawingBufferWidth * this.#gl.drawingBufferHeight}`);
198
194
  }
199
195
  /**
200
196
  * On WebGL context loss, attempts to clear all program variables and then
package/dist/types.d.ts CHANGED
@@ -1,7 +1,11 @@
1
1
  // SPDX-FileCopyrightText: 2025 Chris Duncan <chris@zoso.dev>
2
2
  // SPDX-License-Identifier: GPL-3.0-or-later
3
3
 
4
- import '@webgpu/types'
4
+ declare global {
5
+ interface Window {
6
+ NanoPow: typeof NanoPow
7
+ }
8
+ }
5
9
 
6
10
  /**
7
11
  * Used by work server for inbound requests to `work_generate`.
@@ -11,6 +15,7 @@ import '@webgpu/types'
11
15
  * @param {string} [difficulty=FFFFFFF800000000] - Minimum threshold for a nonce to be valid
12
16
  */
13
17
  type WorkGenerateRequest = {
18
+ [key: string]: string | undefined
14
19
  action: 'work_generate'
15
20
  hash: string
16
21
  difficulty?: string
@@ -24,6 +29,7 @@ type WorkGenerateRequest = {
24
29
  * @param {string} difficulty - BLAKE2b hash result which was compared to specified minimum threshold
25
30
  */
26
31
  type WorkGenerateResponse = {
32
+ [key: string]: string
27
33
  hash: string
28
34
  work: string
29
35
  difficulty: string
@@ -38,6 +44,7 @@ type WorkGenerateResponse = {
38
44
  * @param {string} [difficulty=FFFFFFF800000000] - Minimum threshold for a nonce to be valid
39
45
  */
40
46
  type WorkValidateRequest = {
47
+ [key: string]: string | undefined
41
48
  action: 'work_validate'
42
49
  hash: string
43
50
  work: string
@@ -55,6 +62,7 @@ type WorkValidateRequest = {
55
62
  * @param {string} valid_receive - 1 for true if nonce is valid for receive blocks, else 0 for false
56
63
  */
57
64
  type WorkValidateResponse = {
65
+ [key: string]: string | undefined
58
66
  hash: string
59
67
  work: string
60
68
  difficulty: string
@@ -103,9 +111,8 @@ export type NanoPowOptions = {
103
111
  * Nano proof-of-work using WebGL 2.0.
104
112
  */
105
113
  export declare class NanoPowGl {
114
+ static [key: string]: (...args: any[]) => any
106
115
  #private
107
- /** Drawing buffer width in pixels. */
108
- static get size (): number
109
116
  /**
110
117
  * Constructs canvas, gets WebGL context, initializes buffers, and compiles
111
118
  * shaders.
@@ -153,6 +160,7 @@ export declare class NanoPowGl {
153
160
  */
154
161
  export declare class NanoPowGpu {
155
162
  #private
163
+ static [key: string]: (...args: any[]) => any
156
164
  static init (): Promise<void>
157
165
  static setup (): void
158
166
  static reset (): void
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nano-pow",
3
- "version": "4.0.7",
3
+ "version": "4.0.9",
4
4
  "description": "Proof-of-work generation and validation with WebGPU/WebGL for Nano cryptocurrency.",
5
5
  "keywords": [
6
6
  "nemo",
package/docs/index.js DELETED
@@ -1,38 +0,0 @@
1
- //! SPDX-FileCopyrightText: 2025 Chris Duncan <chris@zoso.dev>
2
- //! SPDX-License-Identifier: GPL-3.0-or-later
3
-
4
- export const cliHelp = `Usage: nano-pow [OPTION]... BLOCKHASH...
5
- Generate work for BLOCKHASH, or multiple work values for BLOCKHASH(es)
6
- BLOCKHASH is a 64-character hexadecimal string. Multiple blockhashes must be separated by whitespace or line breaks.
7
- Prints a 16-character hexadecimal work value to standard output. If using --validate, prints 'true' or 'false' to standard output instead.
8
-
9
- -h, --help show this dialog
10
- -d, --debug enable additional logging output
11
- -j, --json gather all results and output them at once as JSON
12
- -e, --effort=<value> increase demand on GPU processing
13
- -t, --threshold=<value> override the minimum threshold value
14
- -v, --validate=<value> check an existing work value instead of searching for one
15
-
16
- If validating a nonce, it must be a 16-character hexadecimal value.
17
- Effort must be a decimal number between 1-32.
18
- Threshold must be a hexadecimal string between 1-FFFFFFFFFFFFFFFF.
19
-
20
- Report bugs: <bug-nano-pow@zoso.dev>
21
- Full documentation: <https://www.npmjs.com/package/nano-pow>
22
- `
23
-
24
- export const serverHelp = `Usage: Send POST request to server URL to generate or validate Nano proof-of-work
25
-
26
- Generate work for a BLOCKHASH with an optional DIFFICULTY:
27
- curl -d '{ action: "work_generate", hash: BLOCKHASH, difficulty?: DIFFICULTY }'
28
-
29
- Validate WORK previously calculated for a BLOCKHASH with an optional DIFFICULTY:
30
- curl -d '{ action: "work_validate", work: WORK, hash: BLOCKHASH, difficulty?: DIFFICULTY }'
31
-
32
- BLOCKHASH is a 64-character hexadecimal string.
33
- WORK is 16-character hexadecimal string.
34
- DIFFICULTY is a 16-character hexadecimal string (default: FFFFFFF800000000)
35
-
36
- Report bugs: <bug-nano-pow@zoso.dev>
37
- Full documentation: <https://www.npmjs.com/package/nano-pow>
38
- `