nano-pow 4.1.3 → 4.1.5
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 +3 -0
- package/dist/bin/nano-pow.sh +1 -1
- package/dist/bin/server.js +107 -41
- package/dist/main.min.js +47 -41
- package/package.json +2 -2
package/dist/bin/cli.js
CHANGED
|
@@ -124,6 +124,9 @@ log("Starting NanoPow CLI");
|
|
|
124
124
|
const server = spawn(process.execPath, [new URL(import.meta.resolve("./server.js")).pathname], { stdio: ["pipe", "pipe", "pipe", "ipc"] });
|
|
125
125
|
const port = await new Promise((resolve, reject) => {
|
|
126
126
|
server.on("message", (msg) => {
|
|
127
|
+
if (msg.type === "console") {
|
|
128
|
+
log(msg.text);
|
|
129
|
+
}
|
|
127
130
|
if (msg.type === "listening") {
|
|
128
131
|
if (msg.port != null) {
|
|
129
132
|
log(`Server listening on port ${msg.port}`);
|
package/dist/bin/nano-pow.sh
CHANGED
|
@@ -10,7 +10,7 @@ NANO_POW_LOGS="$NANO_POW_HOME"/logs;
|
|
|
10
10
|
mkdir -p "$NANO_POW_LOGS";
|
|
11
11
|
if [ "$1" = '--server' ]; then
|
|
12
12
|
shift;
|
|
13
|
-
node "$SCRIPT_DIR"/server.js
|
|
13
|
+
node "$SCRIPT_DIR"/server.js --max-http-header-size=1024 >> "$NANO_POW_LOGS"/nano-pow-server-$(date -I).log 2>&1 & echo "$!" > "$NANO_POW_HOME"/server.pid;
|
|
14
14
|
sleep 0.1;
|
|
15
15
|
if [ "$(ps | grep $(cat $NANO_POW_HOME/server.pid))" = '' ]; then
|
|
16
16
|
cat $(ls -td "$NANO_POW_LOGS"/* | head -n1);
|
package/dist/bin/server.js
CHANGED
|
@@ -3,16 +3,33 @@
|
|
|
3
3
|
//! SPDX-License-Identifier: GPL-3.0-or-later
|
|
4
4
|
import { launch } from "puppeteer";
|
|
5
5
|
import { subtle } from "node:crypto";
|
|
6
|
-
import { readFile
|
|
6
|
+
import { readFile } from "node:fs/promises";
|
|
7
7
|
import * as http from "node:http";
|
|
8
8
|
import { homedir } from "node:os";
|
|
9
9
|
import { join } from "node:path";
|
|
10
10
|
function log(...args) {
|
|
11
|
-
if (CONFIG.DEBUG)
|
|
11
|
+
if (CONFIG.DEBUG) {
|
|
12
|
+
const text = `${new Date(Date.now()).toLocaleString(Intl.DateTimeFormat().resolvedOptions().locale ?? "en-US", { hour12: false, dateStyle: "medium", timeStyle: "medium" })} NanoPow[${process.pid}]: ${args}`;
|
|
13
|
+
console.log(text);
|
|
14
|
+
process.send?.({ type: "console", text });
|
|
15
|
+
}
|
|
12
16
|
}
|
|
13
17
|
process.title = "NanoPow Server";
|
|
18
|
+
const MAX_BODY_SIZE = 256;
|
|
19
|
+
const MAX_CONNECTIONS = 1024;
|
|
20
|
+
const MAX_HEADER_COUNT = 32;
|
|
21
|
+
const MAX_IDLE_TIME = 5e3;
|
|
22
|
+
const MAX_REQUEST_COUNT = 10;
|
|
14
23
|
const MAX_REQUEST_SIZE = 1024;
|
|
15
|
-
const
|
|
24
|
+
const MAX_REQUEST_TIME = 6e4;
|
|
25
|
+
const requests = /* @__PURE__ */ new Map();
|
|
26
|
+
setInterval(() => {
|
|
27
|
+
for (const [i, t] of requests) {
|
|
28
|
+
if (t.time < Date.now() - MAX_REQUEST_TIME) {
|
|
29
|
+
requests.delete(i);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}, Math.max(MAX_REQUEST_TIME, 0));
|
|
16
33
|
const CONFIG = {
|
|
17
34
|
DEBUG: false,
|
|
18
35
|
EFFORT: 8,
|
|
@@ -50,42 +67,57 @@ process.on("SIGHUP", async () => {
|
|
|
50
67
|
log("Reloading configuration");
|
|
51
68
|
await loadConfig();
|
|
52
69
|
});
|
|
53
|
-
async function respond(res,
|
|
70
|
+
async function respond(res, dataBuffer) {
|
|
54
71
|
let statusCode = 500;
|
|
55
72
|
let headers = { "Content-Type": "application/json" };
|
|
56
|
-
let response = "
|
|
73
|
+
let response = "request failed";
|
|
57
74
|
try {
|
|
58
|
-
const datastring = Buffer.concat(
|
|
75
|
+
const datastring = Buffer.concat(dataBuffer).toString();
|
|
59
76
|
if (Buffer.byteLength(datastring) > MAX_BODY_SIZE) {
|
|
60
|
-
throw new Error("
|
|
77
|
+
throw new Error("Data too large.");
|
|
78
|
+
}
|
|
79
|
+
const data = JSON.parse(datastring);
|
|
80
|
+
if (Object.getPrototypeOf(data) !== Object.prototype) {
|
|
81
|
+
throw new Error("Data corrupted.");
|
|
61
82
|
}
|
|
62
|
-
const { action, hash: hash2, work, difficulty } =
|
|
83
|
+
const { action, hash: hash2, work, difficulty } = data;
|
|
63
84
|
if (action !== "work_generate" && action !== "work_validate") {
|
|
64
|
-
throw new Error("
|
|
85
|
+
throw new Error("Action must be work_generate or work_validate.");
|
|
65
86
|
}
|
|
87
|
+
response = `${action} failed`;
|
|
66
88
|
if (!/^[0-9A-Fa-f]{64}$/.test(hash2 ?? "")) {
|
|
67
|
-
throw new Error("
|
|
89
|
+
throw new Error("Hash must be a 64-character hex string.");
|
|
68
90
|
}
|
|
69
|
-
if (difficulty && !/^[
|
|
70
|
-
throw new Error("
|
|
91
|
+
if (difficulty && !/^[0-9A-Fa-f]{0,16}$/.test(difficulty)) {
|
|
92
|
+
throw new Error("Difficulty must be a hex string between 0-FFFFFFFFFFFFFFFF.");
|
|
71
93
|
}
|
|
72
94
|
if (action === "work_validate" && !/^[0-9A-Fa-f]{16}$/.test(work ?? "")) {
|
|
73
|
-
throw new Error("
|
|
95
|
+
throw new Error("Work must be a 16-character hex string.");
|
|
74
96
|
}
|
|
75
|
-
response = `${action} failed`;
|
|
76
97
|
const options = {
|
|
77
98
|
debug: CONFIG.DEBUG,
|
|
78
99
|
effort: CONFIG.EFFORT,
|
|
79
100
|
difficulty
|
|
80
101
|
};
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
102
|
+
switch (action) {
|
|
103
|
+
case "work_generate": {
|
|
104
|
+
response = JSON.stringify(await page.evaluate(async (np, hash3, options2) => {
|
|
105
|
+
if (np == null) throw new Error("NanoPow not found");
|
|
106
|
+
return await np.work_generate(hash3, options2);
|
|
107
|
+
}, npHandle, hash2, options));
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
case "work_validate": {
|
|
111
|
+
response = JSON.stringify(await page.evaluate(async (np, work2, hash3, options2) => {
|
|
112
|
+
if (np == null) throw new Error("NanoPow not found");
|
|
113
|
+
return await np.work_validate(work2, hash3, options2);
|
|
114
|
+
}, npHandle, work, hash2, options));
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
default: {
|
|
118
|
+
throw new Error("Action must be work_generate or work_validate.");
|
|
119
|
+
}
|
|
120
|
+
}
|
|
89
121
|
statusCode = 200;
|
|
90
122
|
} catch (err) {
|
|
91
123
|
log(err);
|
|
@@ -95,14 +127,35 @@ async function respond(res, data) {
|
|
|
95
127
|
}
|
|
96
128
|
}
|
|
97
129
|
const server = http.createServer((req, res) => {
|
|
130
|
+
const xff = req.headers["x-forwarded-for"];
|
|
131
|
+
const ip = typeof xff === "string" ? xff.split(",")[0].trim().replace(/^::ffff:/, "") : req.socket.remoteAddress;
|
|
132
|
+
if (ip == null) {
|
|
133
|
+
res.writeHead(401).end("Unauthorized");
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const client = requests.get(ip);
|
|
137
|
+
if (ip === "127.0.0.1" || process.send != null || client == null || client.time < Date.now() - MAX_REQUEST_TIME) {
|
|
138
|
+
requests.set(ip, { tokens: MAX_REQUEST_COUNT, time: Date.now() });
|
|
139
|
+
} else {
|
|
140
|
+
if (--client.tokens <= 0) {
|
|
141
|
+
log(`${ip} potential abuse`);
|
|
142
|
+
res.writeHead(429).end("Too Many Requests");
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
98
146
|
let data = [];
|
|
99
147
|
let reqSize = 0;
|
|
100
148
|
if (req.method === "POST") {
|
|
149
|
+
const contentLength = +(req.headers["content-length"] ?? 0);
|
|
150
|
+
if (contentLength == 0 || contentLength > MAX_BODY_SIZE) {
|
|
151
|
+
res.writeHead(413).end("Content Too Large");
|
|
152
|
+
req.socket.destroy();
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
101
155
|
req.on("data", (chunk) => {
|
|
102
156
|
reqSize += chunk.byteLength;
|
|
103
157
|
if (reqSize > MAX_REQUEST_SIZE) {
|
|
104
|
-
res.writeHead(413
|
|
105
|
-
res.end("Content Too Large");
|
|
158
|
+
res.writeHead(413).end("Content Too Large");
|
|
106
159
|
req.socket.destroy();
|
|
107
160
|
return;
|
|
108
161
|
}
|
|
@@ -134,6 +187,13 @@ Full documentation: <https://www.npmjs.com/package/nano-pow>
|
|
|
134
187
|
);
|
|
135
188
|
}
|
|
136
189
|
});
|
|
190
|
+
server.headersTimeout = MAX_IDLE_TIME;
|
|
191
|
+
server.keepAliveTimeout = MAX_IDLE_TIME;
|
|
192
|
+
server.maxConnections = MAX_CONNECTIONS;
|
|
193
|
+
server.maxHeadersCount = MAX_HEADER_COUNT;
|
|
194
|
+
server.on("connection", (c) => {
|
|
195
|
+
c.setTimeout(MAX_IDLE_TIME, () => c.destroy());
|
|
196
|
+
});
|
|
137
197
|
server.on("error", (e) => {
|
|
138
198
|
log("Server error", e);
|
|
139
199
|
try {
|
|
@@ -167,35 +227,41 @@ const browser = await launch({
|
|
|
167
227
|
headless: true,
|
|
168
228
|
args: [
|
|
169
229
|
"--headless=new",
|
|
170
|
-
"--use-angle=vulkan",
|
|
171
|
-
"--enable-features=Vulkan",
|
|
172
230
|
"--disable-vulkan-surface",
|
|
231
|
+
"--enable-features=Vulkan,DefaultANGLEVulkan,VulkanFromANGLE",
|
|
232
|
+
"--enable-gpu",
|
|
173
233
|
"--enable-unsafe-webgpu"
|
|
174
234
|
]
|
|
175
235
|
});
|
|
176
236
|
const page = await browser.newPage();
|
|
177
|
-
|
|
178
|
-
const path = new URL(import.meta.url).pathname;
|
|
179
|
-
const dir = path.slice(0, path.lastIndexOf("/"));
|
|
180
|
-
const filename = join(dir, `${process.pid}.html`);
|
|
181
|
-
await writeFile(filename, '<!DOCTYPE html><link rel="icon" href="data:,">');
|
|
182
|
-
await page.goto(import.meta.resolve(filename));
|
|
183
|
-
await page.waitForFunction(async () => {
|
|
184
|
-
return await navigator["gpu"].requestAdapter();
|
|
185
|
-
});
|
|
186
|
-
const src = `${NanoPow};window.NanoPow=NanoPow;`;
|
|
237
|
+
const src = `${NanoPow};window.NanoPow=NanoPowGpu;`;
|
|
187
238
|
const hash = await subtle.digest("SHA-256", Buffer.from(src));
|
|
188
239
|
const enc = `sha256-${Buffer.from(hash).toString("base64")}`;
|
|
189
|
-
|
|
240
|
+
const body = `
|
|
190
241
|
<!DOCTYPE html>
|
|
191
242
|
<link rel="icon" href="data:,">
|
|
192
243
|
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; base-uri 'none'; form-action 'none'; script-src '${enc}';">
|
|
193
244
|
<script type="module">${src}</script>
|
|
194
|
-
|
|
195
|
-
await
|
|
245
|
+
`;
|
|
246
|
+
await page.setRequestInterception(true);
|
|
247
|
+
page.on("request", async (req) => {
|
|
248
|
+
if (req.isInterceptResolutionHandled()) return;
|
|
249
|
+
if (req.url() === "https://nanopow.invalid/") {
|
|
250
|
+
req.respond({ status: 200, contentType: "text/html", body });
|
|
251
|
+
} else {
|
|
252
|
+
req.continue();
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
page.on("console", (msg) => log(msg.text()));
|
|
256
|
+
await page.goto("https://nanopow.invalid/");
|
|
257
|
+
await page.waitForFunction(async () => {
|
|
258
|
+
return window.NanoPow != null;
|
|
259
|
+
});
|
|
260
|
+
const npHandle = await page.evaluateHandle(() => window.NanoPow);
|
|
196
261
|
log("Puppeteer initialized");
|
|
197
|
-
server.listen(CONFIG.PORT, async () => {
|
|
262
|
+
server.listen(CONFIG.PORT, "127.0.0.1", async () => {
|
|
198
263
|
const { port } = server.address();
|
|
199
|
-
|
|
264
|
+
CONFIG.PORT = port;
|
|
265
|
+
log(`Server listening on port ${port}`);
|
|
200
266
|
process.send?.({ type: "listening", port });
|
|
201
267
|
});
|
package/dist/main.min.js
CHANGED
|
@@ -506,7 +506,7 @@ var NanoPowGl = class _NanoPowGl {
|
|
|
506
506
|
// src/lib/gpu/compute.wgsl
|
|
507
507
|
var compute_default = `//! SPDX-FileCopyrightText: 2025 Chris Duncan <chris@zoso.dev>
|
|
508
508
|
//! SPDX-License-Identifier: GPL-3.0-or-later
|
|
509
|
-
struct UBO{blockhash:array<vec4<u32>,2>,seed:vec2<u32>,difficulty:vec2<u32>};@group(0)@binding(0)var<uniform> ubo:UBO;struct WORK{found:atomic<u32>,nonce:vec2<u32>,result:vec2<u32>};@group(0)@binding(1)var<storage,read_write>work:WORK;const BLAKE2B_IV=array<vec2<u32>,8>(vec2<u32>(0xF3BCC908u,0x6A09E667u),vec2<u32>(0x84CAA73Bu,0xBB67AE85u),vec2<u32>(0xFE94F82Bu,0x3C6EF372u),vec2<u32>(0x5F1D36F1u,0xA54FF53Au),vec2<u32>(0xADE682D1u,0x510E527Fu),vec2<u32>(0x2B3E6C1Fu,0x9B05688Cu),vec2<u32>(0xFB41BD6Bu,0x1F83D9ABu),vec2<u32>(0x137E2179u,0x5BE0CD19u));const BLAKE2B_PARAM=vec2<u32>(0x01010008u,0u);const BLAKE2B_INLEN=vec2<u32>(0x00000028u,0u);const BLAKE2B_FINAL=vec2<u32>(0xFFFFFFFFu,0xFFFFFFFFu);const BLAKE2B_INIT=array<vec2<u32>,16>(BLAKE2B_IV[0u]^ BLAKE2B_PARAM,BLAKE2B_IV[1u],BLAKE2B_IV[2u],BLAKE2B_IV[3u],BLAKE2B_IV[4u],BLAKE2B_IV[5u],BLAKE2B_IV[6u],BLAKE2B_IV[7u],BLAKE2B_IV[0u],BLAKE2B_IV[1u],BLAKE2B_IV[2u],BLAKE2B_IV[3u],BLAKE2B_IV[4u]^ BLAKE2B_INLEN,BLAKE2B_IV[5u],BLAKE2B_IV[6u]^ BLAKE2B_FINAL,BLAKE2B_IV[7u]);fn G(a:ptr<function,vec2<u32>>,b:ptr<function,vec2<u32>>,c:ptr<function,vec2<u32>>,d:ptr<function,vec2<u32>>,m0:vec2<u32>,m1:vec2<u32>){*a+=*b;(*a).y+=u32((*a).x<(*b).x);*a+=m0;(*a).y+=u32((*a).x<m0.x);*d=(*d ^*a).yx;*c+=*d;(*c).y+=u32((*c).x<(*d).x);*b ^=*c;*b=(*b>>vec2(24u))|(*b<<vec2(8u)).yx;*a+=*b;(*a).y+=u32((*a).x<(*b).x);*a+=m1;(*a).y+=u32((*a).x<m1.x);*d ^=*a;*d=(*d>>vec2(16u))|(*d<<vec2(16u)).yx;*c+=*d;(*c).y+=u32((*c).x<(*d).x);*b ^=*c;*b=(*b>>vec2(31u)).yx|(*b<<vec2(1u));}const Z=vec2(0u);var<workgroup> found:bool;var<workgroup> seed:vec2<u32>;var<workgroup> m1:vec2<u32>;var<workgroup> m2:vec2<u32>;var<workgroup> m3:vec2<u32>;var<workgroup> m4:vec2<u32>;@compute @workgroup_size(
|
|
509
|
+
struct UBO{blockhash:array<vec4<u32>,2>,seed:vec2<u32>,difficulty:vec2<u32>};@group(0)@binding(0)var<uniform> ubo:UBO;struct WORK{found:atomic<u32>,nonce:vec2<u32>,result:vec2<u32>};@group(0)@binding(1)var<storage,read_write>work:WORK;const BLAKE2B_IV=array<vec2<u32>,8>(vec2<u32>(0xF3BCC908u,0x6A09E667u),vec2<u32>(0x84CAA73Bu,0xBB67AE85u),vec2<u32>(0xFE94F82Bu,0x3C6EF372u),vec2<u32>(0x5F1D36F1u,0xA54FF53Au),vec2<u32>(0xADE682D1u,0x510E527Fu),vec2<u32>(0x2B3E6C1Fu,0x9B05688Cu),vec2<u32>(0xFB41BD6Bu,0x1F83D9ABu),vec2<u32>(0x137E2179u,0x5BE0CD19u));const BLAKE2B_PARAM=vec2<u32>(0x01010008u,0u);const BLAKE2B_INLEN=vec2<u32>(0x00000028u,0u);const BLAKE2B_FINAL=vec2<u32>(0xFFFFFFFFu,0xFFFFFFFFu);const BLAKE2B_INIT=array<vec2<u32>,16>(BLAKE2B_IV[0u]^ BLAKE2B_PARAM,BLAKE2B_IV[1u],BLAKE2B_IV[2u],BLAKE2B_IV[3u],BLAKE2B_IV[4u],BLAKE2B_IV[5u],BLAKE2B_IV[6u],BLAKE2B_IV[7u],BLAKE2B_IV[0u],BLAKE2B_IV[1u],BLAKE2B_IV[2u],BLAKE2B_IV[3u],BLAKE2B_IV[4u]^ BLAKE2B_INLEN,BLAKE2B_IV[5u],BLAKE2B_IV[6u]^ BLAKE2B_FINAL,BLAKE2B_IV[7u]);fn G(a:ptr<function,vec2<u32>>,b:ptr<function,vec2<u32>>,c:ptr<function,vec2<u32>>,d:ptr<function,vec2<u32>>,m0:vec2<u32>,m1:vec2<u32>){*a+=*b;(*a).y+=u32((*a).x<(*b).x);*a+=m0;(*a).y+=u32((*a).x<m0.x);*d=(*d ^*a).yx;*c+=*d;(*c).y+=u32((*c).x<(*d).x);*b ^=*c;*b=(*b>>vec2(24u))|(*b<<vec2(8u)).yx;*a+=*b;(*a).y+=u32((*a).x<(*b).x);*a+=m1;(*a).y+=u32((*a).x<m1.x);*d ^=*a;*d=(*d>>vec2(16u))|(*d<<vec2(16u)).yx;*c+=*d;(*c).y+=u32((*c).x<(*d).x);*b ^=*c;*b=(*b>>vec2(31u)).yx|(*b<<vec2(1u));}const Z=vec2(0u);var<workgroup> found:bool;var<workgroup> seed:vec2<u32>;var<workgroup> m1:vec2<u32>;var<workgroup> m2:vec2<u32>;var<workgroup> m3:vec2<u32>;var<workgroup> m4:vec2<u32>;@compute @workgroup_size(64)fn search(@builtin(global_invocation_id)global_id:vec3<u32>,@builtin(local_invocation_id)local_id:vec3<u32>){if(local_id.x==0u){found=atomicLoad(&work.found)!=0u;seed=ubo.seed;m1=ubo.blockhash[0u].xy;m2=ubo.blockhash[0u].zw;m3=ubo.blockhash[1u].xy;m4=ubo.blockhash[1u].zw;}workgroupBarrier();if(found){return;}main(global_id,false);}@compute @workgroup_size(1)fn validate(@builtin(global_invocation_id)global_id:vec3<u32>){seed=ubo.seed;m1=ubo.blockhash[0u].xy;m2=ubo.blockhash[0u].zw;m3=ubo.blockhash[1u].xy;m4=ubo.blockhash[1u].zw;main(global_id,true);}fn main(id:vec3<u32>,validate:bool){let m0:vec2<u32>=seed ^ id.xy;var v0:vec2<u32>=vec2<u32>(BLAKE2B_INIT[0u]);var v1:vec2<u32>=vec2<u32>(BLAKE2B_INIT[1u]);var v2:vec2<u32>=vec2<u32>(BLAKE2B_INIT[2u]);var v3:vec2<u32>=vec2<u32>(BLAKE2B_INIT[3u]);var v4:vec2<u32>=vec2<u32>(BLAKE2B_INIT[4u]);var v5:vec2<u32>=vec2<u32>(BLAKE2B_INIT[5u]);var v6:vec2<u32>=vec2<u32>(BLAKE2B_INIT[6u]);var v7:vec2<u32>=vec2<u32>(BLAKE2B_INIT[7u]);var v8:vec2<u32>=vec2<u32>(BLAKE2B_INIT[8u]);var v9:vec2<u32>=vec2<u32>(BLAKE2B_INIT[9u]);var vA:vec2<u32>=vec2<u32>(BLAKE2B_INIT[10u]);var vB:vec2<u32>=vec2<u32>(BLAKE2B_INIT[11u]);var vC:vec2<u32>=vec2<u32>(BLAKE2B_INIT[12u]);var vD:vec2<u32>=vec2<u32>(BLAKE2B_INIT[13u]);var vE:vec2<u32>=vec2<u32>(BLAKE2B_INIT[14u]);var vF:vec2<u32>=vec2<u32>(BLAKE2B_INIT[15u]);G(&v0,&v4,&v8,&vC,m0,m1);G(&v1,&v5,&v9,&vD,m2,m3);G(&v2,&v6,&vA,&vE,m4,Z);G(&v3,&v7,&vB,&vF,Z,Z);G(&v0,&v5,&vA,&vF,Z,Z);G(&v1,&v6,&vB,&vC,Z,Z);G(&v2,&v7,&v8,&vD,Z,Z);G(&v3,&v4,&v9,&vE,Z,Z);G(&v0,&v4,&v8,&vC,Z,Z);G(&v1,&v5,&v9,&vD,m4,Z);G(&v2,&v6,&vA,&vE,Z,Z);G(&v3,&v7,&vB,&vF,Z,Z);G(&v0,&v5,&vA,&vF,m1,Z);G(&v1,&v6,&vB,&vC,m0,m2);G(&v2,&v7,&v8,&vD,Z,Z);G(&v3,&v4,&v9,&vE,Z,m3);G(&v0,&v4,&v8,&vC,Z,Z);G(&v1,&v5,&v9,&vD,Z,m0);G(&v2,&v6,&vA,&vE,Z,m2);G(&v3,&v7,&vB,&vF,Z,Z);G(&v0,&v5,&vA,&vF,Z,Z);G(&v1,&v6,&vB,&vC,m3,Z);G(&v2,&v7,&v8,&vD,Z,m1);G(&v3,&v4,&v9,&vE,Z,m4);G(&v0,&v4,&v8,&vC,Z,Z);G(&v1,&v5,&v9,&vD,m3,m1);G(&v2,&v6,&vA,&vE,Z,Z);G(&v3,&v7,&vB,&vF,Z,Z);G(&v0,&v5,&vA,&vF,m2,Z);G(&v1,&v6,&vB,&vC,Z,Z);G(&v2,&v7,&v8,&vD,m4,m0);G(&v3,&v4,&v9,&vE,Z,Z);G(&v0,&v4,&v8,&vC,Z,m0);G(&v1,&v5,&v9,&vD,Z,Z);G(&v2,&v6,&vA,&vE,m2,m4);G(&v3,&v7,&vB,&vF,Z,Z);G(&v0,&v5,&vA,&vF,Z,m1);G(&v1,&v6,&vB,&vC,Z,Z);G(&v2,&v7,&v8,&vD,Z,Z);G(&v3,&v4,&v9,&vE,m3,Z);G(&v0,&v4,&v8,&vC,m2,Z);G(&v1,&v5,&v9,&vD,Z,Z);G(&v2,&v6,&vA,&vE,m0,Z);G(&v3,&v7,&vB,&vF,Z,m3);G(&v0,&v5,&vA,&vF,m4,Z);G(&v1,&v6,&vB,&vC,Z,Z);G(&v2,&v7,&v8,&vD,Z,Z);G(&v3,&v4,&v9,&vE,m1,Z);G(&v0,&v4,&v8,&vC,Z,Z);G(&v1,&v5,&v9,&vD,m1,Z);G(&v2,&v6,&vA,&vE,Z,Z);G(&v3,&v7,&vB,&vF,m4,Z);G(&v0,&v5,&vA,&vF,m0,Z);G(&v1,&v6,&vB,&vC,Z,m3);G(&v2,&v7,&v8,&vD,Z,m2);G(&v3,&v4,&v9,&vE,Z,Z);G(&v0,&v4,&v8,&vC,Z,Z);G(&v1,&v5,&v9,&vD,Z,Z);G(&v2,&v6,&vA,&vE,Z,m1);G(&v3,&v7,&vB,&vF,m3,Z);G(&v0,&v5,&vA,&vF,Z,m0);G(&v1,&v6,&vB,&vC,Z,m4);G(&v2,&v7,&v8,&vD,Z,Z);G(&v3,&v4,&v9,&vE,m2,Z);G(&v0,&v4,&v8,&vC,Z,Z);G(&v1,&v5,&v9,&vD,Z,Z);G(&v2,&v6,&vA,&vE,Z,m3);G(&v3,&v7,&vB,&vF,m0,Z);G(&v0,&v5,&vA,&vF,Z,m2);G(&v1,&v6,&vB,&vC,Z,Z);G(&v2,&v7,&v8,&vD,m1,m4);G(&v3,&v4,&v9,&vE,Z,Z);G(&v0,&v4,&v8,&vC,Z,m2);G(&v1,&v5,&v9,&vD,Z,m4);G(&v2,&v6,&vA,&vE,Z,Z);G(&v3,&v7,&vB,&vF,m1,Z);G(&v0,&v5,&vA,&vF,Z,Z);G(&v1,&v6,&vB,&vC,Z,Z);G(&v2,&v7,&v8,&vD,m3,Z);G(&v3,&v4,&v9,&vE,Z,m0);G(&v0,&v4,&v8,&vC,m0,m1);G(&v1,&v5,&v9,&vD,m2,m3);G(&v2,&v6,&vA,&vE,m4,Z);G(&v3,&v7,&vB,&vF,Z,Z);G(&v0,&v5,&vA,&vF,Z,Z);G(&v1,&v6,&vB,&vC,Z,Z);G(&v2,&v7,&v8,&vD,Z,Z);G(&v3,&v4,&v9,&vE,Z,Z);G(&v0,&v4,&v8,&vC,Z,Z);G(&v1,&v5,&v9,&vD,m4,Z);G(&v2,&v6,&vA,&vE,Z,Z);G(&v3,&v7,&vB,&vF,Z,Z);G(&v0,&v5,&vA,&vF,m1,Z);G(&v2,&v7,&v8,&vD,Z,Z);var result:vec2<u32>=BLAKE2B_INIT[0u]^ v0 ^ v8;if(validate||((result.y>ubo.difficulty.y||(result.y==ubo.difficulty.y&&result.x>=ubo.difficulty.x))&&atomicLoad(&work.found)==0u)){atomicStore(&work.found,1u);work.nonce=m0;work.result=result;}return;}`;
|
|
510
510
|
|
|
511
511
|
// src/lib/gpu/index.ts
|
|
512
512
|
//! SPDX-FileCopyrightText: 2025 Chris Duncan <chris@zoso.dev>
|
|
@@ -524,7 +524,9 @@ var NanoPowGpu = class _NanoPowGpu {
|
|
|
524
524
|
static #cpuBuffer;
|
|
525
525
|
static #uboBuffer;
|
|
526
526
|
static #uboView;
|
|
527
|
+
static #resultView;
|
|
527
528
|
static #bindGroupLayout;
|
|
529
|
+
static #bindGroup;
|
|
528
530
|
static #searchPipeline;
|
|
529
531
|
static #validatePipeline;
|
|
530
532
|
// Initialize WebGPU
|
|
@@ -539,7 +541,7 @@ var NanoPowGpu = class _NanoPowGpu {
|
|
|
539
541
|
if (!(device instanceof GPUDevice)) throw new Error("WebGPU device failed to load.");
|
|
540
542
|
device.lost?.then(this.reset);
|
|
541
543
|
this.#device = device;
|
|
542
|
-
this.setup();
|
|
544
|
+
await this.setup();
|
|
543
545
|
} catch (err) {
|
|
544
546
|
throw new Error("WebGPU initialization failed.", { cause: err });
|
|
545
547
|
} finally {
|
|
@@ -547,7 +549,7 @@ var NanoPowGpu = class _NanoPowGpu {
|
|
|
547
549
|
}
|
|
548
550
|
this.#isInitialized = true;
|
|
549
551
|
}
|
|
550
|
-
static setup() {
|
|
552
|
+
static async setup() {
|
|
551
553
|
if (this.#device == null) throw new Error(`WebGPU device failed to load.`);
|
|
552
554
|
this.#gpuBuffer = this.#device.createBuffer({
|
|
553
555
|
size: 32,
|
|
@@ -588,6 +590,23 @@ var NanoPowGpu = class _NanoPowGpu {
|
|
|
588
590
|
module: shaderModule
|
|
589
591
|
}
|
|
590
592
|
});
|
|
593
|
+
this.#bindGroup = this.#device.createBindGroup({
|
|
594
|
+
layout: this.#bindGroupLayout,
|
|
595
|
+
entries: [
|
|
596
|
+
{
|
|
597
|
+
binding: 0,
|
|
598
|
+
resource: {
|
|
599
|
+
buffer: this.#uboBuffer
|
|
600
|
+
}
|
|
601
|
+
},
|
|
602
|
+
{
|
|
603
|
+
binding: 1,
|
|
604
|
+
resource: {
|
|
605
|
+
buffer: this.#gpuBuffer
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
]
|
|
609
|
+
});
|
|
591
610
|
this.#validatePipeline = this.#device.createComputePipeline({
|
|
592
611
|
layout: this.#device.createPipelineLayout({
|
|
593
612
|
bindGroupLayouts: [this.#bindGroupLayout]
|
|
@@ -597,6 +616,10 @@ var NanoPowGpu = class _NanoPowGpu {
|
|
|
597
616
|
module: shaderModule
|
|
598
617
|
}
|
|
599
618
|
});
|
|
619
|
+
const cmd = this.#device.createCommandEncoder();
|
|
620
|
+
cmd.beginComputePass().end();
|
|
621
|
+
this.#device.queue.submit([cmd.finish()]);
|
|
622
|
+
await this.#device.queue.onSubmittedWorkDone();
|
|
600
623
|
console.log(`NanoPow WebGPU initialized. Recommended effort: ${Math.max(1, Math.floor(navigator.hardwareConcurrency / 2))}`);
|
|
601
624
|
}
|
|
602
625
|
static reset() {
|
|
@@ -652,45 +675,26 @@ var NanoPowGpu = class _NanoPowGpu {
|
|
|
652
675
|
this.#device.queue.writeBuffer(this.#uboBuffer, 0, this.#uboView);
|
|
653
676
|
this.#device.queue.writeBuffer(this.#gpuBuffer, 0, this.#bufferReset);
|
|
654
677
|
this.#device.queue.writeBuffer(this.#cpuBuffer, 0, this.#bufferReset);
|
|
655
|
-
const bindGroup = this.#device.createBindGroup({
|
|
656
|
-
layout: this.#bindGroupLayout,
|
|
657
|
-
entries: [
|
|
658
|
-
{
|
|
659
|
-
binding: 0,
|
|
660
|
-
resource: {
|
|
661
|
-
buffer: this.#uboBuffer
|
|
662
|
-
}
|
|
663
|
-
},
|
|
664
|
-
{
|
|
665
|
-
binding: 1,
|
|
666
|
-
resource: {
|
|
667
|
-
buffer: this.#gpuBuffer
|
|
668
|
-
}
|
|
669
|
-
}
|
|
670
|
-
]
|
|
671
|
-
});
|
|
672
678
|
const commandEncoder = this.#device.createCommandEncoder();
|
|
673
679
|
const passEncoder = commandEncoder.beginComputePass();
|
|
674
680
|
passEncoder.setPipeline(pipeline);
|
|
675
|
-
passEncoder.setBindGroup(0, bindGroup);
|
|
681
|
+
passEncoder.setBindGroup(0, this.#bindGroup);
|
|
676
682
|
passEncoder.dispatchWorkgroups(passes, passes);
|
|
677
683
|
passEncoder.end();
|
|
678
684
|
commandEncoder.copyBufferToBuffer(this.#gpuBuffer, 0, this.#cpuBuffer, 0, 32);
|
|
679
685
|
this.#device.queue.submit([commandEncoder.finish()]);
|
|
680
|
-
let data = null;
|
|
681
686
|
try {
|
|
682
687
|
await this.#cpuBuffer.mapAsync(GPUMapMode.READ);
|
|
683
688
|
await this.#device.queue.onSubmittedWorkDone();
|
|
684
|
-
|
|
689
|
+
this.#resultView = new DataView(this.#cpuBuffer.getMappedRange().slice(0));
|
|
685
690
|
this.#cpuBuffer.unmap();
|
|
686
691
|
} catch (err) {
|
|
687
692
|
console.warn(`Error getting data from GPU. ${err}`);
|
|
688
693
|
this.#cpuBuffer.unmap();
|
|
689
694
|
this.reset();
|
|
690
695
|
}
|
|
691
|
-
if (this.#debug) console.log("gpuBuffer data",
|
|
692
|
-
if (
|
|
693
|
-
return data;
|
|
696
|
+
if (this.#debug) console.log("gpuBuffer data", this.#resultView);
|
|
697
|
+
if (this.#resultView == null) throw new Error(`Failed to get data from buffer.`);
|
|
694
698
|
}
|
|
695
699
|
/**
|
|
696
700
|
* Finds a nonce that satisfies the Nano proof-of-work requirements.
|
|
@@ -706,7 +710,7 @@ var NanoPowGpu = class _NanoPowGpu {
|
|
|
706
710
|
setTimeout(async () => {
|
|
707
711
|
const result2 = this.work_generate(hash, options);
|
|
708
712
|
resolve(result2);
|
|
709
|
-
},
|
|
713
|
+
}, 100);
|
|
710
714
|
});
|
|
711
715
|
}
|
|
712
716
|
if (this.#isInitialized === false) this.init();
|
|
@@ -718,14 +722,14 @@ var NanoPowGpu = class _NanoPowGpu {
|
|
|
718
722
|
throw new TypeError(`Invalid difficulty ${options.difficulty}`);
|
|
719
723
|
}
|
|
720
724
|
}
|
|
721
|
-
const difficulty = typeof options?.difficulty !== "bigint" || options.difficulty <
|
|
725
|
+
const difficulty = typeof options?.difficulty !== "bigint" || options.difficulty < 0n || options.difficulty > 0xffffffffffffffffn ? 0xfffffff800000000n : options.difficulty;
|
|
722
726
|
const effort = typeof options?.effort !== "number" || options.effort < 1 || options.effort > 32 ? 2048 : options.effort * 256;
|
|
723
727
|
this.#debug = !!options?.debug;
|
|
724
728
|
if (this.#debug) console.log("NanoPowGpu.work_generate()");
|
|
725
729
|
if (this.#debug) console.log("blockhash", hash);
|
|
726
730
|
if (this.#debug) console.log("search options", JSON.stringify(options, (k, v) => typeof v === "bigint" ? v.toString(16) : v));
|
|
727
731
|
let loads = 0;
|
|
728
|
-
while (this.#device == null && loads < 20) {
|
|
732
|
+
while (this.#device == null && loads++ < 20) {
|
|
729
733
|
await new Promise((resolve) => {
|
|
730
734
|
setTimeout(resolve, 500);
|
|
731
735
|
});
|
|
@@ -738,16 +742,17 @@ var NanoPowGpu = class _NanoPowGpu {
|
|
|
738
742
|
let start = performance.now();
|
|
739
743
|
let nonce = 0n;
|
|
740
744
|
let result = 0n;
|
|
745
|
+
let random = BigInt(Math.floor(Math.random() * 4294967295));
|
|
746
|
+
let seed = random;
|
|
741
747
|
do {
|
|
742
748
|
start = performance.now();
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
const seed = BigInt(random0) << 32n | BigInt(random1);
|
|
749
|
+
random = BigInt(Math.floor(Math.random() * 4294967295));
|
|
750
|
+
seed = (seed & 0xffffffffn) << 32n | random;
|
|
746
751
|
if (this.#debug) console.log("seed", seed.toString(16).padStart(16, "0"));
|
|
747
|
-
|
|
748
|
-
const found = !!
|
|
749
|
-
nonce =
|
|
750
|
-
result =
|
|
752
|
+
await this.#dispatch(this.#searchPipeline, seed, hash, difficulty, effort);
|
|
753
|
+
const found = !!this.#resultView.getUint32(0);
|
|
754
|
+
nonce = this.#resultView.getBigUint64(8, true);
|
|
755
|
+
result = this.#resultView.getBigUint64(16, true);
|
|
751
756
|
this.#busy = !found;
|
|
752
757
|
times.push(performance.now() - start);
|
|
753
758
|
} while (this.#busy);
|
|
@@ -776,7 +781,7 @@ var NanoPowGpu = class _NanoPowGpu {
|
|
|
776
781
|
setTimeout(async () => {
|
|
777
782
|
const result2 = this.work_validate(work, hash, options);
|
|
778
783
|
resolve(result2);
|
|
779
|
-
},
|
|
784
|
+
}, 100);
|
|
780
785
|
});
|
|
781
786
|
}
|
|
782
787
|
if (this.#isInitialized === false) this.init();
|
|
@@ -788,7 +793,7 @@ var NanoPowGpu = class _NanoPowGpu {
|
|
|
788
793
|
throw new TypeError(`Invalid difficulty ${options.difficulty}`);
|
|
789
794
|
}
|
|
790
795
|
}
|
|
791
|
-
const difficulty = typeof options?.difficulty !== "bigint" || options.difficulty <
|
|
796
|
+
const difficulty = typeof options?.difficulty !== "bigint" || options.difficulty < 0n || options.difficulty > 0xffffffffffffffffn ? 0xfffffff800000000n : options.difficulty;
|
|
792
797
|
this.#debug = !!options?.debug;
|
|
793
798
|
if (this.#debug) console.log("NanoPowGpu.work_validate()");
|
|
794
799
|
if (this.#debug) console.log("blockhash", hash);
|
|
@@ -807,10 +812,11 @@ var NanoPowGpu = class _NanoPowGpu {
|
|
|
807
812
|
let nonce = 0n;
|
|
808
813
|
const seed = BigInt(`0x${work}`);
|
|
809
814
|
if (this.#debug) console.log("work", work);
|
|
810
|
-
|
|
811
|
-
nonce =
|
|
812
|
-
result =
|
|
815
|
+
await this.#dispatch(this.#validatePipeline, seed, hash, difficulty, 1);
|
|
816
|
+
nonce = this.#resultView.getBigUint64(8, true);
|
|
817
|
+
result = this.#resultView.getBigUint64(16, true);
|
|
813
818
|
this.#busy = false;
|
|
819
|
+
if (seed !== nonce) throw new Error("Result does not match work");
|
|
814
820
|
if (this.#debug) console.log("nonce", nonce, nonce.toString(16).padStart(16, "0"));
|
|
815
821
|
if (this.#debug) console.log("result", result, result.toString(16).padStart(16, "0"));
|
|
816
822
|
const response = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nano-pow",
|
|
3
|
-
"version": "4.1.
|
|
3
|
+
"version": "4.1.5",
|
|
4
4
|
"description": "Proof-of-work generation and validation with WebGPU/WebGL for Nano cryptocurrency.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"nemo",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"url": "git+https://zoso.dev/nano-pow.git"
|
|
46
46
|
},
|
|
47
47
|
"scripts": {
|
|
48
|
-
"benchmark": "npm run build && ./dist/bin/nano-pow.sh --benchmark
|
|
48
|
+
"benchmark": "npm run build && ./dist/bin/nano-pow.sh --benchmark 100",
|
|
49
49
|
"build": "rm -rf {dist,types} && tsc && node esbuild.mjs && cp -p src/bin/nano-pow.sh dist/bin",
|
|
50
50
|
"prepare": "npm run build",
|
|
51
51
|
"start": "./dist/bin/nano-pow.sh --server",
|