UNPKG

nano-pow

Version:

Proof-of-work generation and validation with WebGPU/WebGL/WASM for Nano cryptocurrency.

380 lines (371 loc) 11.1 kB
// src/utils/api-support/wasm.ts //! SPDX-FileCopyrightText: 2025 Chris Duncan <chris@codecow.com> //! SPDX-License-Identifier: GPL-3.0-or-later var wasm = { isSupported: false }; Object.defineProperty(wasm, "isSupported", { get: async function() { let isWasmSupported = false; try { const wasmBuffer = new Uint8Array([ 0, 97, 115, 109, 1, 0, 0, 0, // WASM magic header and version 1, 4, // Type section, 4 byte descriptor 1, 96, 0, 0, // 1 type, type function, 0 params, 0 returns 3, 2, // Function section, 2 byte descriptor 1, 0, // 1 function, ID 0 10, 23, // Code section, 23 bytes 1, 21, // 1 function body, 21 bytes 0, // 0 local variables 253, 12, // v128.const 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // assign i64x2 values 26, 11 // Drop result, end function ]); const module = await WebAssembly.compile(wasmBuffer); const instance = await WebAssembly.instantiate(module); isWasmSupported = instance instanceof WebAssembly.Instance; } catch (err) { console.warn("WASM is not supported in this environment.\n", err.message ?? err); isWasmSupported = false; } finally { delete this.isSupported; this.isSupported = isWasmSupported; return this.isSupported; } } }); // src/utils/api-support/webgl.ts //! SPDX-FileCopyrightText: 2025 Chris Duncan <chris@codecow.com> //! SPDX-License-Identifier: GPL-3.0-or-later var webgl = { isSupported: false }; Object.defineProperty(webgl, "isSupported", { get: async function() { let isWebglSupported = false; try { const gl = new OffscreenCanvas(0, 0)?.getContext?.("webgl2"); isWebglSupported = gl instanceof WebGL2RenderingContext && !gl.isContextLost(); } catch (err) { console.warn("WebGL is not supported in this environment.\n", err); isWebglSupported = false; } finally { delete this.isSupported; this.isSupported = isWebglSupported; return this.isSupported; } } }); // src/utils/api-support/webgpu.ts //! SPDX-FileCopyrightText: 2025 Chris Duncan <chris@codecow.com> //! SPDX-License-Identifier: GPL-3.0-or-later var webgpu = { isSupported: false }; Object.defineProperty(webgpu, "isSupported", { get: async function() { let isWebgpuSupported = false; try { const adapter = await navigator?.gpu?.requestAdapter?.(); const device = await adapter?.requestDevice?.(); isWebgpuSupported = adapter instanceof GPUAdapter && device instanceof GPUDevice; } catch (err) { console.warn("WebGPU is not supported in this environment.\n", err); isWebgpuSupported = false; } finally { delete this.isSupported; this.isSupported = isWebgpuSupported; return this.isSupported; } } }); // src/utils/api-support/index.ts //! SPDX-FileCopyrightText: 2025 Chris Duncan <chris@codecow.com> //! SPDX-License-Identifier: GPL-3.0-or-later var ApiSupport = { cpu: { isSupported: true }, wasm, webgl, webgpu }; // src/utils/bigint.ts //! SPDX-FileCopyrightText: 2025 Chris Duncan <chris@codecow.com> //! SPDX-License-Identifier: GPL-3.0-or-later function big(value) { switch (typeof value) { case "bigint": case "boolean": case "number": { return BigInt(value); } case "string": { const v = value.trim().replace(/^0[Xx]/, "").replace(/[Nn]$/, ""); if (!isHexN(v, null)) throw TypeError("invalid hex string", { cause: value }); return BigInt(`0x${v}`); } default: { throw TypeError(`can't convert value to BigInt`); } } } function bigHex(int, length = 0) { return int.toString(16).padStart(length, "0"); } function bigToU32(int, length) { const u32 = new Uint32Array(length); bigintToUintArray(u32, int); return u32; } function bigToU64(int, length) { const u64 = new BigUint64Array(length); bigintToUintArray(u64, int); return u64; } function bigintToUintArray(out, int) { if (int < 0n) int = ~(int - 1n); const bits = out instanceof BigUint64Array ? 64n : 32n; const mask = (1n << bits) - 1n; for (let i = out.length - 1; i >= 0 && int > 0n; i--) { const v = int & mask; out[i] = bits === 64n ? v : Number(v); int >>= bits; } } // src/utils/cache.ts //! SPDX-FileCopyrightText: 2025 Chris Duncan <chris@codecow.com> //! SPDX-License-Identifier: GPL-3.0-or-later var STORAGE_KEY = "NanoPowCache"; var storage = globalThis.localStorage ?? { records: /* @__PURE__ */ new Map(), getItem(key) { return this.records.get(key); }, removeItem(key) { this.records.delete(key); }, setItem(key, item) { this.storage.set(key, item); } }; function eq(a, b) { return typeof a === "string" && /^([a-f0-9]{2})+$/i.test(a) && BigInt(`0x${a}`) === b; } var Cache = { clear() { storage.removeItem(STORAGE_KEY); }, delete(hash) { const item = storage.getItem(STORAGE_KEY); if (item == null) return; const cache = JSON.parse(item); for (let i = 0; i < cache.length; i++) { if (cache[i].hash === hash) { cache.splice(i, 1); } } storage.setItem(STORAGE_KEY, JSON.stringify(cache)); }, search(hash, difficulty) { const item = storage.getItem(STORAGE_KEY); if (item == null) return null; const cache = JSON.parse(item); const match = cache.find((c) => c.hash === hash && eq(c.difficulty, difficulty)); return match ?? null; }, store(result) { const item = storage.getItem(STORAGE_KEY) ?? "[]"; const cache = JSON.parse(item); if (cache.push(result) > 1e3) cache.shift(); storage.setItem(STORAGE_KEY, JSON.stringify(cache)); return result; } }; // src/utils/logger.ts //! SPDX-FileCopyrightText: 2025 Chris Duncan <chris@codecow.com> //! SPDX-License-Identifier: GPL-3.0-or-later var Logger = class { isEnabled = false; groups = {}; groupStart(name) { if (this.isEnabled) { console.groupCollapsed(name); this.groups[name] = true; } } groupEnd(name) { if (this.groups[name]) { console.groupEnd(); delete this.groups[name]; } } log(...args) { if (this.isEnabled) { const datetime = new Date(Date.now()).toLocaleString(Intl.DateTimeFormat().resolvedOptions().locale ?? "en-US", { hour12: false, dateStyle: "medium", timeStyle: "medium" }); for (let i = 0; i < args.length; i++) { if (typeof args[i] === "string") { args[i] = args[i].replace(datetime, "").trimStart(); } if (args[i] instanceof Error) { if ("stack" in args[i]) { args.splice(i + 1, 0, args[i].stack); } else if ("message" in args[i]) { args.splice(i + 1, 0, args[i].message); } } } const entry = `${datetime} ${globalThis.process?.title ?? "NanoPow"}[${globalThis.process?.pid ?? "browser"}]:`; console.log(entry, ...args); globalThis.process?.send?.({ type: "console", data: `${entry} ${args}` }); } } }; // src/utils/queue.ts //! SPDX-FileCopyrightText: 2025 Chris Duncan <chris@codecow.com> //! SPDX-License-Identifier: GPL-3.0-or-later var Queue = class { #isIdle; #queue; constructor() { this.#isIdle = true; this.#queue = []; } #process = () => { const { task, resolve, reject, args } = this.#queue.shift() ?? {}; this.#isIdle = !task; task?.(...args).then(resolve).catch(reject).finally(this.#process); }; async add(task, ...args) { if (typeof task !== "function") throw new TypeError("task is not a function"); return new Promise((resolve, reject) => { this.#queue.push({ task, resolve, reject, args }); if (this.#isIdle) this.#process(); }); } async prioritize(task, ...args) { if (typeof task !== "function") throw new TypeError("task is not a function"); return new Promise((resolve, reject) => { if (typeof task !== "function") reject("task is not a function"); this.#queue.unshift({ task, resolve, reject, args }); if (this.#isIdle) this.#process(); }); } }; // src/utils/index.ts //! SPDX-FileCopyrightText: 2025 Chris Duncan <chris@codecow.com> //! SPDX-License-Identifier: GPL-3.0-or-later var SEND = 0xfffffff800000000n; var RECEIVE = 0xfffffe0000000000n; var MAX_HASH = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffn; function isHexN(value, byteLength) { if (typeof value !== "string") return false; const length = byteLength === null ? "+" : `{${byteLength << 1}}`; const r = new RegExp(`^[0-9a-f]${length}$`, "i"); return r.test(value); } function isHex8(value) { return isHexN(value, 8); } function isHex32(value) { return isHexN(value, 32); } function stats(times) { if (times == null || times.length === 0) return null; const count = times.length; const truncatedStart = Math.floor(count * 0.1); const truncatedEnd = count - truncatedStart; const truncatedCount = truncatedEnd - truncatedStart; let min = Number.MAX_SAFE_INTEGER; let logarithms, max, median, reciprocals, total; logarithms = max = median = reciprocals = total = 0; let truncatedMin = Number.MAX_SAFE_INTEGER; let truncatedLogarithms, truncatedMax, truncatedReciprocals, truncatedTotal; truncatedLogarithms = truncatedMax = truncatedReciprocals = truncatedTotal = 0; times.sort((a, b) => a - b); for (let i = 0; i < count; i++) { const time = times[i]; total += time; logarithms += Math.log(time); reciprocals += 1 / time; min = Math.min(min, time); max = Math.max(max, time); if (i === Math.floor((count - 1) / 2)) median = time; if (i === Math.floor(count / 2) && count % 2 === 0) median = (median + time) / 2; } for (let i = truncatedStart; i < truncatedEnd; i++) { const time = times[i]; truncatedTotal += time; truncatedLogarithms += Math.log(time); truncatedReciprocals += 1 / time; truncatedMin = Math.min(truncatedMin, time); truncatedMax = Math.max(truncatedMax, time); } return { count, total, rate: 1e3 * count / total, min, max, median, arithmetic: total / count, geometric: Math.exp(logarithms / count), harmonic: count / reciprocals, truncatedCount, truncatedTotal, truncatedRate: 1e3 * truncatedCount / truncatedTotal, truncatedMin, truncatedMax, truncatedArithmetic: truncatedTotal / truncatedCount, truncatedGeometric: Math.exp(truncatedLogarithms / truncatedCount), truncatedHarmonic: truncatedCount / truncatedReciprocals }; } export { ApiSupport, Cache, Logger, MAX_HASH, Queue, RECEIVE, SEND, big, bigHex, bigToU32, bigToU64, isHex32, isHex8, isHexN, stats }; //# sourceMappingURL=index.js.map