## Installation
```console
-npm i nano-pow
+$ npm i nano-pow
```
+NanoPow can also be installed globally to add the `nano-pow` command to your
+environment. To learn more, see [#Executables](#executables).
## Usage
### Import
<script type="module">
(async () => {
const { NanoPow } = await import('https://cdn.jsdelivr.net/npm/nano-pow@latest')
- const work = await NanoPow.search(some_hash)
+ const { work } = await NanoPow.work_generate(some_hash)
console.log(work)
})()
</script>
```
-### Search
+### Generate
```javascript
// `hash` is a 64-char hex string
const hash = '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef'
-const work = await NanoPow.search(hash)
+const { work } = await NanoPow.work_generate(hash)
// Result is a 16-char hex string
```
const work = 'fedcba0987654321'
// `hash` is a 64-char hex string
const hash = '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef'
-const isValid = await NanoPow.validate(work, hash)
+const { valid } = await NanoPow.work_validate(work, hash)
// Result is a boolean
```
### Options
```javascript
const options = {
- // default 0xFFFFFFF8 for send/change blocks
+ // default 0xFFFFFFF800000000 for send/change blocks
threshold: number,
// default 8, valid range 1-32
effort: number,
// default false
debug: true
}
-const work = await NanoPow.search(hash, options)
+const { work } = await NanoPow.work_generate(hash, options)
```
-### Command Line
+## Executables
NanoPow can be installed globally and executed from the command line. This is
useful for systems without a graphical interface.
```console
$ npm -g i nano-pow
-$ nano-pow --help # view command documentation
+$ nano-pow --help # view abbreviated CLI help
+$ man nano-pow # view full manual
+```
+Ensure you have proper permissions on your
+[npm `prefix`](https://docs.npmjs.com/cli/v11/commands/npm-prefix) directory and
+have configured your `PATH` accordingly.
+
+For example, this adds a user-specific directory for local binaries to `PATH`
+upon login and configures `npm prefix` to use it for global installations:
+```console
+$ echo 'PATH="$HOME/.local/bin:$PATH"' >> .bashrc
+$ npm config set prefix="$HOME/.local"
+```
+### Command Line
+NanoPow provides a shell command—`nano-pow`—to accomodate systems
+without a graphical user interface. It launches a headless Chrome browser using
+`puppeteer` to access the required WebGPU or WebGL APIs. Use the `--global` flag
+when installing to add the executable script to your system.
+```console
+$ npm i -g nano-pow
+```
+Some examples are provided below, and for full documentation, read the manual
+with `man nano-pow`.
+
+```console
+$ # Generate a work value using default settings and debugging output enabled.
+$ nano-pow --debug 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
+```
+```console
+$ # Generate work using customized behavior with options.
+$ nano-pow --effort 32 --threshold FFFFFFC000000000 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
+```
+```console
+$ # Validate an existing work nonce against a blockhash.
+nano-pow --validate fedcba9876543210 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
+```
+```console
+$ # Process blockhashes in batches to reduce the initial startup overhead.
+$ nano-pow 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef [...]
+$ # OR
+nano-pow $(cat /path/to/hashes/file)
+$ # OR
+$ cat /path/to/hashes/file | nano-pow
+```
+
+### Server
+NanoPow also provides a basic work server similar to the one included in the
+official Nano node software. The installed command will launch the server in a
+detached process, and you can also start it yourself to customize behavior by
+executing the server script directly.
+
+`PORT` can be passed as an environment variable and defaults to 3000 if not
+specified.
+
+`NANO_POW_EFFORT` can also be passed as an environment variable to increase
+or decrease the demand on the GPU and defaults to 8 if not specified.
+
+```console
+$ # Launch the server and detach from the current session
+$ PORT=8080 nano-pow --server
+$ # View process ID for "NanoPow Server"
+$ cat ~/.nano-pow/server.pid
+$ # Display list of server logs
+$ ls ~/.nano-pow/logs/
+$ # Find process ID manually
+$ ps aux | grep NanoPow
+```
+Work is generated or validated by sending an HTTP `POST` request to the
+configured hostname or IP address of the machine. Some basic help is available
+via `GET` request.
+```console
+$ # Generate a work value
+$ curl -d '{
+ "action": "work_generate",
+ "hash": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
+}' localhost:3000
+```
+```console
+$ # Validate a work value
+$ curl -d '{
+ "action": "work_validate",
+ "work": "e45835c3b291c3d1",
+ "hash": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
+}' localhost:3000
```
## Notes
* 𝘵𝘩𝘳𝘦𝘴𝘩𝘰𝘭𝘥 is 0xFFFFFFF800000000 for send/change blocks and 0xFFFFFE0000000000
for receive/open/epoch blocks.
-The threshold is implemented in code as only the first 32 bits due to WGSL only
-supporting u32 integer types, but the result is the same. For example, if
-checking whether a two-digit number xx > 55, and it is known that the first
-digit is 6, it is also automatically known that xx is greater than 55. The
-default threshold used is the send/change difficulty. Any other threshold can be
-specified in practice.
-
The BLAKE2b implementation has been optimized to the extreme for this package
-due to the very narrow use case to which it is applied. The compute shader is
-consequently immense, but the goal is to squeeze every last bit of speed and
-performance out of it.
+due to the very narrow use case to which it is applied. The compute shader used
+by the WebGPU implementation is consequently immense, but the goal is to squeeze
+every last bit of speed and performance out of it.
## Tests
-`test.html` in the source repository contains some basic tests to compare the
-speed of this tool. Feel free to check out how your system fares.
+A few basic tests are availabe in the source repository.
+* `test/index.html` in the source repository contains a web interface to change
+execution options and compare results.
+* `test/script.sh` starts the `nano-pow` server and sends some basic requests.
## Building
1. Clone source
1. Install dev dependencies
1. Compile, minify, and bundle
-```bash
-git clone https://zoso.dev/nano-pow.git
-cd nano-pow
-npm i
-npm run build
+```console
+$ git clone https://zoso.dev/nano-pow.git
+$ cd nano-pow
+$ npm i
```
## Reporting Bugs
-h, --help show this dialog
-d, --debug enable additional logging output
- -j, --json format output as JSON
+ -j, --json gather all results and output them at once as JSON
-e, --effort=<value> increase demand on GPU processing
-t, --threshold=<value> override the minimum threshold value
-v, --validate=<value> check an existing work value instead of searching for one
If validating a nonce, it must be a 16-character hexadecimal value.
Effort must be a decimal number between 1-32.
-Threshold must be a hexadecimal string between 0-FFFFFFFF.
+Threshold must be a hexadecimal string between 1-FFFFFFFFFFFFFFFF.
Report bugs: <bug-nano-pow@zoso.dev>
Full documentation: <https://www.npmjs.com/package/nano-pow>
.SH DESCRIPTION
Generate work for \fIBLOCKHASH\fR, or multiple work values for \fIBLOCKHASH\fR(es).
.PP
+If the \fI--server\fR option is provided as the first argument, all other options are ignored and the NanoPow server is started.
+.PP
\fIBLOCKHASH\fR is a 64-character hexadecimal string. Multiple blockhashes must be separated by whitespace or line breaks.
.PP
-Prints a 16-character hexadecimal work value to standard output. If \fB--validate\fR is used, prints 'true' or 'false' to standard output.
+Prints a 16-character hexadecimal work value, the BLAKE2b result, and the originating hash to standard output as a Javascript object.
+.PP
+If \fB--validate\fR is used, the original work value is returned instead along with validation flags.
.SH OPTIONS
.TP
+\fB--server\fR
+Start work server (see SERVER below). Must be the first argument in order to be recognized.
+.TP
\fB\-h\fR, \fB\-\-help\fR
Show this help dialog and exit.
.TP
Enable additional logging output.
.TP
\fB\-j\fR, \fB\-\-json\fR
-Format output as JSON.
+Format final output of all hashes as a JSON array of stringified values instead of incrementally returning Javascript objects for each result.
.TP
\fB\-e\fR, \fB\-\-effort\fR=\fIEFFORT\fR
Increase demand on GPU processing. Must be between 1 and 32 inclusive.
.TP
\fB\-t\fR, \fB\-\-threshold\fR=\fITHRESHOLD\fR
-Override the minimum threshold value. Higher values increase difficulty. Must be a hexadecimal string between 0 and FFFFFFFF inclusive.
+Override the minimum threshold value. Higher values increase difficulty. Must be a hexadecimal string between 1 and FFFFFFFFFFFFFFFF inclusive.
.TP
\fB\-v\fR, \fB\-\-validate\fR=\fIWORK\fR
-Check an existing work value instead of searching for one.
+Check an existing work value instead of searching for one. If you pass multiple blockhashes, the work value will be validated against each one.
+
+.SH SERVER
+Calling \fBnano-pow\fR with the \fI--server\fR option will start the NanoPow work server in a detached process.
+.PP
+It provides work generation and validation via HTTP requests in a similar, but not identical, fashion as the official Nano node.
+.PP
+More specifically, it does not support the \fIuse_peers\fR, \fImultiplier\fR, \fIaccount\fR, \fIversion\fR, \fIblock\fR, and \fIjson_block\fR options.
+.PP
+By default, the server listens on port 3000. To use a different port, set the \fBPORT\fR environment variable before starting the server.
+
+.PP
+.EX
+$ PORT=8080 nano-pow --server
+.EE
+
+.PP
+The server process ID (PID) is saved to \fB~/.nano-pow/server.pid\fR. Log files are stored in \fB~/.nano-pow/logs/\fR.
+.TP
+To stop the server, terminate it using its PID.
+.EX
+$ cat ~/.nano-pow/server.pid
+12345
+$ kill 12345
+.EE
+.TP
+Alternatively, use \fBpgrep\fR to find the PID by process name:
+.EX
+$ pgrep "NanoPow Server"
+12345
+$ kill $(pgrep "NanoPow Server")
+.EE
+
+.PP
+The server accepts HTTP \fBPOST\fR requests to the server's root path (\fB/\fR). Requests should be in JSON format and include an \fBaction\fR field to specify the desired operation.
+.PP
+The following actions are supported:
+.TP
+\fBwork_generate\fR
+Generate a work value for a given blockhash. Requires a \fBhash\fR field containing the 64-character hexadecimal blockhash.
+.TP
+\fBwork_validate\fR
+Validate an existing work value against a blockhash. Requires \fBwork\fR field containing the 16-character hexadecimal work value and a \fBhash\fR field containing the 64-character hexadecimal blockhash.
+.PP
+
-.SH EXAMPLES
+.SH EXAMPLES - CLI
.PP
-Search for a work nonce for a blockhash using the default threshold 0xFFFFFFF8:
+Search for a work nonce for a blockhash using the default threshold 0xFFFFFFF800000000:
.EX
$ nano-pow \fB0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\fR
.EE
.PP
Search for a work nonce using a custom threshold and increased effort:
.EX
-$ nano-pow \fB\-t fffffe00 \-e 32 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\fR
+$ nano-pow \fB\-t fffffe0000000000 \-e 32 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\fR
.EE
.PP
$ nano-pow \fB\-d \-v fedcba9876543210 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\fR
.EE
+.SH EXAMPLES - SERVER
+.PP
+Generate a work value:
+.EX
+$ curl -d '{
+ "action": "work_generate",
+ "hash": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
+}' localhost:3000
+.EE
+
+.PP
+Validate a work value:
+.EX
+$ curl -d '{
+ "action": "work_validate",
+ "work": "e45835c3b291c3d1",
+ "hash": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
+}' localhost:3000
+.EE
+
.SH AUTHOR
Written by Chris Duncan.
//! SPDX-FileCopyrightText: 2025 Chris Duncan <chris@zoso.dev>
//! SPDX-License-Identifier: GPL-3.0-or-later
/// <reference types="@webgpu/types" />
+
import * as crypto from 'node:crypto'
import * as fs from 'node:fs/promises'
import * as readline from 'node:readline/promises'
mkdir -p "$NANO_POW_LOGS";
if [ "$1" = '--server' ]; then
shift;
- node "$SCRIPT_DIR"/server.js "$@" > "$NANO_POW_LOGS"/nano-pow-server-$(date +%s).log 2>&1 & echo "$!" > "$NANO_POW_HOME"/server.pid;
+ node "$SCRIPT_DIR"/server.js > "$NANO_POW_LOGS"/nano-pow-server-$(date +%s).log 2>&1 & echo "$!" > "$NANO_POW_HOME"/server.pid;
else
node "$SCRIPT_DIR"/cli.js "$@";
fi;
#!/usr/bin/env node
//! SPDX-FileCopyrightText: 2025 Chris Duncan <chris@zoso.dev>
//! SPDX-License-Identifier: GPL-3.0-or-later
+
import * as crypto from 'node:crypto'
import * as dns from 'node:dns/promises'
import * as fs from 'node:fs/promises'
import { NanoPowOptions, WorkGenerateRequest, WorkGenerateResponse, WorkValidateRequest, WorkValidateResponse } from '../types.js'
const PORT = process.env.PORT || 3000
+const EFFORT = +(process.env.NANO_POW_EFFORT || 8)
function log (...args) {
console.log(new Date(Date.now()).toLocaleString(), 'NanoPow', args)
}
try {
- const result = await page.evaluate(async (args: WorkGenerateRequest): Promise<WorkGenerateResponse> => {
- const options: NanoPowOptions = {
- debug: true
- }
- if (args.difficulty) options.threshold = BigInt(`0x${args.difficulty}`)
+ const result = await page.evaluate(async (json: WorkGenerateRequest, options: NanoPowOptions): Promise<WorkGenerateResponse> => {
+ if (json.difficulty) options.threshold = BigInt(`0x${json.difficulty}`)
// @ts-expect-error
- return await window.NanoPow.work_generate(args.hash, options)
- }, json)
+ return await window.NanoPow.work_generate(json.hash, options)
+ }, json, { debug: true, effort: EFFORT })
res.writeHead(200, { 'Content-Type': 'application/json' })
res.end(JSON.stringify(result))
} catch (err) {
}
try {
- const result: WorkValidateResponse = await page.evaluate(async (args: WorkValidateRequest): Promise<WorkValidateResponse> => {
- const options: NanoPowOptions = {
- debug: true
- }
- if (args.difficulty) options.threshold = BigInt(`0x${args.difficulty}`)
+ const result: WorkValidateResponse = await page.evaluate(async (json: WorkValidateRequest, options: NanoPowOptions): Promise<WorkValidateResponse> => {
+ if (json.difficulty) options.threshold = BigInt(`0x${json.difficulty}`)
//@ts-expect-error
- return await window.NanoPow.work_validate(args.work, args.hash, options)
- }, json)
+ return await window.NanoPow.work_validate(json.work, json.hash, options)
+ }, json, { debug: true, effort: EFFORT })
res.writeHead(200, { 'Content-Type': 'application/json' })
res.end(JSON.stringify(result))
} catch (err) {
# SPDX-FileCopyrightText: 2025 Chris Duncan <chris@zoso.dev>
# SPDX-License-Identifier: GPL-3.0-or-later
+export NANO_POW_EFFORT=24
SCRIPT_LINK=$(readlink -f "$0");
SCRIPT_DIR=$(dirname "$SCRIPT_LINK");
NANO_POW_HOME="$HOME"/.nano-pow;
NANO_POW_LOGS="$NANO_POW_HOME"/logs;
-"$SCRIPT_DIR"/../dist/bin/nano-pow.sh --server
+PORT=3001 "$SCRIPT_DIR"/../dist/bin/nano-pow.sh --server
sleep 2s
printf '\nGet documentation\n'
-curl localhost:3000
+curl localhost:3001
printf '\nExpect error. Server should not crash when bad data is received like missing end quote\n'
-curl -d '{ "action": "work_validate", "work": "47c83266398728cf", "hash: "92BA74A7D6DC7557F3EDA95ADC6341D51AC777A0A6FF0688A5C492AB2B2CB40D }' localhost:3000
+curl -d '{ "action": "work_validate", "work": "47c83266398728cf", "hash: "92BA74A7D6DC7557F3EDA95ADC6341D51AC777A0A6FF0688A5C492AB2B2CB40D }' localhost:3001
printf '\nValidate good hashes\n'
-curl -d '{ "action": "work_validate", "work": "47c83266398728cf", "hash": "92BA74A7D6DC7557F3EDA95ADC6341D51AC777A0A6FF0688A5C492AB2B2CB40D" }' localhost:3000
-curl -d '{ "action": "work_validate", "work": "4a8fb104eebbd336", "hash": "8797585D56B8AEA3A62899C31FC088F9BE849BA8298A88E94F6E3112D4E55D01" }' localhost:3000
-curl -d '{ "action": "work_validate", "work": "326f310d629a8a98", "hash": "204076E3364D16A018754FF67D418AB2FBEB38799FF9A29A1D5F9E34F16BEEEA", "difficulty": "ffffffff00000000" }' localhost:3000
-curl -d '{ "action": "work_validate", "work": "c5d5d6f7c5d6ccd1", "hash": "281E89AC73B1082B464B9C3C1168384F846D39F6DF25105F8B4A22915E999117" }' localhost:3000
-curl -d '{ "action": "work_validate", "work": "6866c1ac3831a891", "hash": "7069D9CD1E85D6204301D254B0927F06ACC794C9EA5DF70EA5578458FB597090", "difficulty": "fffffe0000000000" }' localhost:3000
+curl -d '{ "action": "work_validate", "work": "47c83266398728cf", "hash": "92BA74A7D6DC7557F3EDA95ADC6341D51AC777A0A6FF0688A5C492AB2B2CB40D" }' localhost:3001
+curl -d '{ "action": "work_validate", "work": "4a8fb104eebbd336", "hash": "8797585D56B8AEA3A62899C31FC088F9BE849BA8298A88E94F6E3112D4E55D01" }' localhost:3001
+curl -d '{ "action": "work_validate", "work": "326f310d629a8a98", "hash": "204076E3364D16A018754FF67D418AB2FBEB38799FF9A29A1D5F9E34F16BEEEA", "difficulty": "ffffffff00000000" }' localhost:3001
+curl -d '{ "action": "work_validate", "work": "c5d5d6f7c5d6ccd1", "hash": "281E89AC73B1082B464B9C3C1168384F846D39F6DF25105F8B4A22915E999117" }' localhost:3001
+curl -d '{ "action": "work_validate", "work": "6866c1ac3831a891", "hash": "7069D9CD1E85D6204301D254B0927F06ACC794C9EA5DF70EA5578458FB597090", "difficulty": "fffffe0000000000" }' localhost:3001
printf '\nValidate bad hashes\n'
-curl -d '{ "action": "work_validate", "work": "0000000000000000", "hash": "0000000000000000000000000000000000000000000000000000000000000000" }' localhost:3000
-curl -d '{ "action": "work_validate", "work": "c5d5d6f7c5d6ccd1", "hash": "BA1E946BA3D778C2F30A83D44D2132CC6EEF010D8D06FF10A8ABD0100D8FB47E" }' localhost:3000
-curl -d '{ "action": "work_validate", "work": "ae238556213c3624", "hash": "BF41D87DA3057FDC6050D2B00C06531F89F4AA6195D7C6C2EAAF15B6E703F8F6", "difficulty": "ffffffff00000000" }' localhost:3000
-curl -d '{ "action": "work_validate", "work": "29a9ae0236990e2e", "hash": "32721F4BD2AFB6F6A08D41CD0DF3C0D9C0B5294F68D0D12422F52B28F0800B5F" }' localhost:3000
-curl -d '{ "action": "work_validate", "work": "7d903b18d03f9820", "hash": "39C57C28F904DFE4012288FFF64CE80C0F42601023A9C82108E8F7B2D186C150", "difficulty": "fffffe0000000000" }' localhost:3000
-curl -d '{ "action": "work_validate", "work": "e45835c3b291c3d1", "hash": "9DCD89E2B92FD59D7358C2C2E4C225DF94C88E187B27882F50FEFC3760D3994F", "difficulty": "ffffffff00000000" }' localhost:3000
+curl -d '{ "action": "work_validate", "work": "0000000000000000", "hash": "0000000000000000000000000000000000000000000000000000000000000000" }' localhost:3001
+curl -d '{ "action": "work_validate", "work": "c5d5d6f7c5d6ccd1", "hash": "BA1E946BA3D778C2F30A83D44D2132CC6EEF010D8D06FF10A8ABD0100D8FB47E" }' localhost:3001
+curl -d '{ "action": "work_validate", "work": "ae238556213c3624", "hash": "BF41D87DA3057FDC6050D2B00C06531F89F4AA6195D7C6C2EAAF15B6E703F8F6", "difficulty": "ffffffff00000000" }' localhost:3001
+curl -d '{ "action": "work_validate", "work": "29a9ae0236990e2e", "hash": "32721F4BD2AFB6F6A08D41CD0DF3C0D9C0B5294F68D0D12422F52B28F0800B5F" }' localhost:3001
+curl -d '{ "action": "work_validate", "work": "7d903b18d03f9820", "hash": "39C57C28F904DFE4012288FFF64CE80C0F42601023A9C82108E8F7B2D186C150", "difficulty": "fffffe0000000000" }' localhost:3001
+curl -d '{ "action": "work_validate", "work": "e45835c3b291c3d1", "hash": "9DCD89E2B92FD59D7358C2C2E4C225DF94C88E187B27882F50FEFC3760D3994F", "difficulty": "ffffffff00000000" }' localhost:3001
printf '\nGenerate\n'
-curl -d '{ "action": "work_generate", "hash": "92BA74A7D6DC7557F3EDA95ADC6341D51AC777A0A6FF0688A5C492AB2B2CB40D" }' localhost:3000
-curl -d '{ "action": "work_generate", "hash": "204076E3364D16A018754FF67D418AB2FBEB38799FF9A29A1D5F9E34F16BEEEA", "difficulty": "ffffffff00000000" }' localhost:3000
-curl -d '{ "action": "work_generate", "hash": "7069D9CD1E85D6204301D254B0927F06ACC794C9EA5DF70EA5578458FB597090", "difficulty": "fffffe0000000000" }' localhost:3000
+curl -d '{ "action": "work_generate", "hash": "92BA74A7D6DC7557F3EDA95ADC6341D51AC777A0A6FF0688A5C492AB2B2CB40D" }' localhost:3001
+curl -d '{ "action": "work_generate", "hash": "204076E3364D16A018754FF67D418AB2FBEB38799FF9A29A1D5F9E34F16BEEEA", "difficulty": "ffffffff00000000" }' localhost:3001
+curl -d '{ "action": "work_generate", "hash": "7069D9CD1E85D6204301D254B0927F06ACC794C9EA5DF70EA5578458FB597090", "difficulty": "fffffe0000000000" }' localhost:3001
kill $(cat "$HOME"/.nano-pow/server.pid) && rm "$HOME"/.nano-pow/server.pid