pow - A simple Proof-of-Work implementation for web apps
This package provides a simple Proof-of-Work implementation for web apps. It is designed to abstract away the complexity of Proof-of-Work and provide a simple interface to use it in your web app to verify users and prevent spam.
The library is unopinionated about what Frontend and Backend frameworks you use and how you transfer data between them.
Installation
npm install @vantezzen/pow
Example
A fully client-side demo can be accessed at https://vantezzen.github.io/pow/.
The example implementation can be found in the demo folder.
Usage
This library contains two main classes: PowServer
should run in a secure environment (e.g. your NodeJS backend) and PowClient
should run in your client (e.g. a React frontend).
Your PowServer
contains a secret key that is used to generate and verify challenges. The PowClient
can then take a challenge generated by the server to solve.
Both the server and client are written to be environment-agnositic and can be used inside NodeJS and the browser.
Server
import { PowServer, PowCrypto } from "@vantezzen/pow";
// You can use any secret key you want or use `PowCrypto` to generate a random key instead
const powCrypto = new PowCrypto();
const secret = await powCrypto.generateSecret();
// Please note that the secret key must be the same between generating a challenge and verifying it.
// If you use multiple servers, you should use a shared secret key for all of them.
// Create a new server instance
const server = new PowServer(secret);
// Generate a challenge. This is just an encrypted string that you can send to the client.
const challenge = await server.generateChallenge();
// You should now send the challenge string over to the client using your preferred method.
// In this example, the challenge is sent using socket.io
socket.emit("challenge", challenge, async (nonce) => {
// We now received a nonce from the client and can verify it.
const result = await powServer.verifyProofOfWork(challenge, nonce);
// The result is a `PowVerifyResult` object that contains the result of the verification.
if (result.isValid) {
// The nonce is valid and the user can be verified
} else {
// The nonce is invalid and the user should be rejected
console.log("Error", result.error);
}
});
Client
import { PowClient } from "@vantezzen/pow";
// Create a new client instance
const client = new PowClient();
// Again, the challenge can be transferred to the client using any method you want.
// In this example, the challenge is received using socket.io
socket.on("challenge", async (challenge, callback) => {
// We now received a challenge from the server and can solve it.
try {
const nonce = await powClient.solveChallenge(challenge);
// We now have a nonce that we can send back to the server.
// In this example, the nonce is sent using socket.io
callback(nonce);
} catch (error) {
// The challenge could not be solved. This is most likely because the proof of work took to long and timed out.
console.log("Error", error);
}
});
Changing the difficulty
By default, the proof of work difficulty is set to 4
. This means that the hash of the challenge and nonce must start with 0000
to be considered valid. On average, it takes 1 second to solve a challenge with difficulty 4
on a modern computer.
If you want to increase the difficulty, you can do so by passing a higher difficulty to the PowServer
and PowClient
constructor. You should always consider updating the validity
option of the PowServer
as well as the timeout
option of the PowClient
to prevent the challenge from being discarded if the client takes too long. The difficulty needs to be the same on the server and client.
const server = new PowServer(secret, {
difficulty: 6,
validity: 30000,
});
const client = new PowClient({
difficulty: 6,
timeout: 30000,
});
API
PowServer
constructor(secret: string, options?: PowServerOptions)
Creates a new PowServer
instance.
-
secret
- The secret key that is used to generate and verify challenges. This should be a random string of at least 32 characters. -
options
- Optional options for the server.-
difficulty
- The difficulty of the proof of work (This is the number of leading zeros that the hash of the challenge and nonce must have). Defaults to4
. -
validity
- The validity of the proof of work challenge in milliseconds. Defaults to15000
(15 seconds). If the client takes longer than this to solve the challenge, the challenge will be considered invalid to prevent replay attacks.
-
generateChallenge(): Promise<string>
Generates a new challenge.
verifyProofOfWork(challenge: string, nonce: string): Promise<PowVerifyResult>
Verifies a proof of work challenge.
-
challenge
- The challenge that was generated by the server. -
nonce
- The nonce that was generated by the client.
PowClient
constructor(options?: PowClientOptions)
Creates a new PowClient
instance.
-
options
- Optional options for the client.-
difficulty
- The difficulty of the proof of work (This is the number of leading zeros that the hash of the challenge and nonce must have). Defaults to4
. -
timeout
- Time in milliseconds after which the proof of work should be stopped. Defaults to10000
(10 seconds). If the client takes longer than this to solve the challenge, the challenge will be considered too difficult.
-
solveChallenge(challenge: string): Promise<string>
Solves a challenge.
-
challenge
- The challenge that was generated by the server.
PowCrypto
generateSecret(): Promise<string>
Generates a new random secret key.
PowVerifyResult
isValid: boolean
Whether the proof of work is valid.
error?: string
The error message if the proof of work is invalid.
License
MIT