This commit is contained in:
Daniel Regeci
2024-04-05 21:27:24 -03:00
parent e3ca2a5941
commit 281380aaf4
40 changed files with 1451 additions and 118 deletions

1
.gitignore vendored
View File

@@ -1,3 +1,4 @@
/node_modules
/benchmark/node_modules
.TODO
.DS_Store

6
.husky/pre-commit Normal file
View File

@@ -0,0 +1,6 @@
npm test
npm run build
npm run denoify
git add deno_dist
git add dist
git add cjs

View File

@@ -42,6 +42,8 @@ Parameters:
- `salt?: string`: Optional salt string. If not provided, a random salt will be generated.
- `saltLength?: number` Optional maximum lenght of the random salt (in bytes, defaults to 12).
Returns: `Promise<Challenge>`
### `verifySolution(payload, hmacKey)`
Verifies an ALTCHA solution. The payload can be a Base64-encoded JSON payload (as submitted by the widget) or an object.
@@ -51,6 +53,69 @@ Parameters:
- `payload: string | Payload`
- `hmacKey: string`
Returns: `Promise<boolean>`
### `solveChallenge(challenge, salt, algorithm?, max?, start?)`
Finds a solution to the given challenge.
Parameters:
- `challenge: string` (required): The challenge hash.
- `salt: string` (required): The challenge salt.
- `algorithm?: string`: Optional algorithm (default: `SHA-256`).
- `max?: string`: Optional `maxnumber` to iterate to (default: 1e6).
- `start?: string`: Optional starting number (default: 0).
Returns: `{ controller: AbortController, promise: Promise<Solution | null> }`
### `solveChallengeWorkers(workerScript, concurrency, challenge, salt, algorithm?, max?, start?)`
Finds a solution to the given challenge with [Web Workers](https://developer.mozilla.org/en-US/docs/Web/API/Worker/Worker) running concurrently.
Parameters:
- `workerScript: string` (required): The path or URL of the worker script.
- `concurrency: number` (required): The concurrency (number of workers).
- `challenge: string` (required): The challenge hash.
- `salt: string` (required): The challenge salt.
- `algorithm?: string`: Optional algorithm (default: `SHA-256`).
- `max?: string`: Optional `maxnumber` to iterate to (default: 1e6).
- `start?: string`: Optional starting number (default: 0).
Returns: `Promise<Solution | null>`
Usage with `altcha-lib/worker`:
```ts
import { solveChallengeWorkers } from 'altcha-lib';
const solution = await solveChallengeWorkers(
'altcha-lib/worker', // URL to
8, // spawn 8 workers
challenge,
salt,
);
```
## Benchmarks
```
> solveChallenge()
- n = 1,000............................... 317 ops/s ±2.63%
- n = 10,000.............................. 32 ops/s ±1.88%
- n = 100,000............................. 3 ops/s ±0.34%
- n = 500,000............................. 0 ops/s ±0.32%
> solveChallengeWorkers() (8 workers)
- n = 1,000............................... 66 ops/s ±3.44%
- n = 10,000.............................. 31 ops/s ±4.28%
- n = 100,000............................. 7 ops/s ±4.40%
- n = 500,000............................. 1 ops/s ±2.49%
```
Run with Bun on MacBook Pro M3-Pro. See [/benchmark](/benchmark/) folder for more details.
## License
MIT

94
benchmark/bench.ts Normal file
View File

@@ -0,0 +1,94 @@
import { benchmark } from './helpers.js';
import {
createChallenge,
solveChallenge,
solveChallengeWorkers,
} from '../lib/index.js';
const hmacKey = 'test';
const workers = 8;
const workerScript = await import.meta.resolve!('../lib/worker.ts');
const challenge1 = await createChallenge({
hmacKey,
maxNumber: 1000,
number: 1000,
});
const challenge2 = await createChallenge({
hmacKey,
maxNumber: 10000,
number: 10000,
});
const challenge3 = await createChallenge({
hmacKey,
maxNumber: 100000,
number: 100000,
});
const challenge4 = await createChallenge({
hmacKey,
maxNumber: 500000,
number: 500000,
});
await benchmark('solveChallenge()', (bench) => {
bench
.add('n = 1,000', async () => {
await solveChallenge(challenge1.challenge, challenge1.salt).promise;
})
.add('n = 10,000', async () => {
await solveChallenge(challenge2.challenge, challenge2.salt).promise;
})
.add('n = 100,000', async () => {
await solveChallenge(challenge3.challenge, challenge3.salt).promise;
})
.add('n = 500,000', async () => {
await solveChallenge(challenge4.challenge, challenge4.salt).promise;
})
});
await benchmark(`solveChallengeWorkers() (${workers} workers)`, (bench) => {
bench
.add('n = 1,000', async () => {
await solveChallengeWorkers(
workerScript,
workers,
challenge1.challenge,
challenge1.salt,
challenge1.algorithm,
challenge1.max,
);
})
.add('n = 10,000', async () => {
await solveChallengeWorkers(
workerScript,
workers,
challenge2.challenge,
challenge2.salt,
challenge2.algorithm,
challenge2.max,
);
})
.add('n = 100,000', async () => {
await solveChallengeWorkers(
workerScript,
workers,
challenge3.challenge,
challenge3.salt,
challenge3.algorithm,
challenge3.max,
);
})
.add('n = 500,000', async () => {
await solveChallengeWorkers(
workerScript,
workers,
challenge4.challenge,
challenge4.salt,
challenge4.algorithm,
challenge4.max,
);
});
});

27
benchmark/helpers.ts Normal file
View File

@@ -0,0 +1,27 @@
import { Bench } from 'tinybench';
const NAME_MAX_LEN = 40;
export async function benchmark(name: string, initFn: (bench: Bench) => void, duration: number = 500) {
const bench = new Bench({
time: duration,
throws: true,
warmupTime: 2000,
warmupIterations: 100,
});
initFn(bench);
await bench.run();
console.log('>', name);
for (let row of bench.table()) {
if (row) {
console.log(
'-',
row['Task Name'].slice(0, NAME_MAX_LEN).padEnd(NAME_MAX_LEN, '.'),
row['ops/sec'].padStart(10, ' '),
'ops/s',
row['Margin'],
);
}
}
console.log('');
}

18
benchmark/package-lock.json generated Normal file
View File

@@ -0,0 +1,18 @@
{
"name": "benchmark",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"devDependencies": {
"tinybench": "^2.6.0"
}
},
"node_modules/tinybench": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.6.0.tgz",
"integrity": "sha512-N8hW3PG/3aOoZAN5V/NSAEDz0ZixDSSt5b/a05iqtpgfLWMSVuCo7w0k2vVvEjdrIoeGqZzweX2WlyioNIHchA==",
"dev": true
}
}
}

9
benchmark/package.json Normal file
View File

@@ -0,0 +1,9 @@
{
"type": "module",
"scripts": {
"bench": "npx tsx bench.ts"
},
"devDependencies": {
"tinybench": "^2.6.0"
}
}

1
cjs/dist/crypto.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
export {};

6
cjs/dist/crypto.js vendored Normal file
View File

@@ -0,0 +1,6 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
if (!('crypto' in globalThis)) {
// eslint-disable-next-line @typescript-eslint/no-var-requires
globalThis.crypto = require('node:crypto').webcrypto;
}

View File

@@ -1,4 +1,6 @@
import './crypto.js';
import type { Algorithm } from './types.js';
export declare const encoder: TextEncoder;
export declare function ab2hex(ab: ArrayBuffer | Uint8Array): string;
export declare function hash(algorithm: Algorithm, str: string): Promise<string>;
export declare function hmac(algorithm: Algorithm, str: string, secret: string): Promise<string>;

15
cjs/dist/helpers.js vendored
View File

@@ -1,11 +1,8 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.randomInt = exports.randomBytes = exports.hmac = exports.hash = exports.ab2hex = void 0;
const encoder = new TextEncoder();
if (!('crypto' in globalThis)) {
// eslint-disable-next-line @typescript-eslint/no-var-requires
globalThis.crypto = require('node:crypto').webcrypto;
}
exports.randomInt = exports.randomBytes = exports.hmac = exports.hash = exports.ab2hex = exports.encoder = void 0;
require("./crypto.js");
exports.encoder = new TextEncoder();
function ab2hex(ab) {
return [...new Uint8Array(ab)]
.map((x) => x.toString(16).padStart(2, '0'))
@@ -13,15 +10,15 @@ function ab2hex(ab) {
}
exports.ab2hex = ab2hex;
async function hash(algorithm, str) {
return ab2hex(await crypto.subtle.digest(algorithm.toUpperCase(), encoder.encode(str)));
return ab2hex(await crypto.subtle.digest(algorithm.toUpperCase(), exports.encoder.encode(str)));
}
exports.hash = hash;
async function hmac(algorithm, str, secret) {
const key = await crypto.subtle.importKey('raw', encoder.encode(secret), {
const key = await crypto.subtle.importKey('raw', exports.encoder.encode(secret), {
name: 'HMAC',
hash: algorithm,
}, false, ['sign', 'verify']);
return ab2hex(await crypto.subtle.sign('HMAC', key, encoder.encode(str)));
return ab2hex(await crypto.subtle.sign('HMAC', key, exports.encoder.encode(str)));
}
exports.hmac = hmac;
function randomBytes(length) {

7
cjs/dist/index.d.ts vendored
View File

@@ -1,3 +1,8 @@
import type { Challenge, ChallengeOptions, Payload } from './types.js';
import type { Challenge, ChallengeOptions, Payload, Solution } from './types.js';
export declare function createChallenge(options: ChallengeOptions): Promise<Challenge>;
export declare function verifySolution(payload: string | Payload, hmacKey: string): Promise<boolean>;
export declare function solveChallenge(challenge: string, salt: string, algorithm?: string, max?: number, start?: number): {
promise: Promise<Solution | null>;
controller: AbortController;
};
export declare function solveChallengeWorkers(workerScript: string | URL, concurrency: number, challenge: string, salt: string, algorithm?: string, max?: number, startNumber?: number): Promise<Solution | null>;

87
cjs/dist/index.js vendored
View File

@@ -1,20 +1,21 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.verifySolution = exports.createChallenge = void 0;
exports.solveChallengeWorkers = exports.solveChallenge = exports.verifySolution = exports.createChallenge = void 0;
const helpers_js_1 = require("./helpers.js");
const DEFAULT_MAX_NUMBER = 1e6;
const DEFAULT_SALT_LEN = 12;
const DEFAULT_ALG = 'SHA-256';
async function createChallenge(options) {
const algorithm = options.algorithm || DEFAULT_ALG;
const maxNumber = options.maxNumber || DEFAULT_MAX_NUMBER;
const max = options.maxNumber || DEFAULT_MAX_NUMBER;
const saltLength = options.saltLength || DEFAULT_SALT_LEN;
const salt = options.salt || (0, helpers_js_1.ab2hex)((0, helpers_js_1.randomBytes)(saltLength));
const number = options.number === void 0 ? (0, helpers_js_1.randomInt)(maxNumber) : options.number;
const number = options.number === void 0 ? (0, helpers_js_1.randomInt)(max) : options.number;
const challenge = await (0, helpers_js_1.hash)(algorithm, salt + number);
return {
algorithm,
challenge,
max,
salt,
signature: await (0, helpers_js_1.hmac)(algorithm, challenge, options.hmacKey),
};
@@ -34,3 +35,83 @@ async function verifySolution(payload, hmacKey) {
check.signature === payload.signature);
}
exports.verifySolution = verifySolution;
function solveChallenge(challenge, salt, algorithm = 'SHA-256', max = 1e6, start = 0) {
const controller = new AbortController();
const promise = new Promise((resolve, reject) => {
const startTime = Date.now();
const next = (n) => {
if (controller.signal.aborted || n > max) {
resolve(null);
}
else {
hashChallenge(salt, n, algorithm)
.then((t) => {
if (t === challenge) {
resolve({
number: n,
took: Date.now() - startTime,
});
}
else {
next(n + 1);
}
})
.catch(reject);
}
};
next(start);
});
return {
promise,
controller,
};
}
exports.solveChallenge = solveChallenge;
async function solveChallengeWorkers(workerScript, concurrency, challenge, salt, algorithm = 'SHA-256', max = 1e6, startNumber = 0) {
const workers = [];
if (concurrency < 1) {
throw new Error('Wrong number of workers configured.');
}
if (concurrency > 16) {
throw new Error('Too many workers. Max. 16 allowed workers.');
}
for (let i = 0; i < concurrency; i++) {
workers.push(new Worker(workerScript, {
type: 'module',
}));
}
const step = Math.ceil(max / concurrency);
const solutions = await Promise.all(workers.map((worker, i) => {
const start = startNumber + i * step;
return new Promise((resolve) => {
worker.addEventListener('message', (message) => {
if (message.data) {
for (const w of workers) {
if (w !== worker) {
w.postMessage({ type: 'abort' });
}
}
}
resolve(message.data);
});
worker.postMessage({
payload: {
algorithm,
challenge,
max: start + step,
salt,
start,
},
type: 'work',
});
});
}));
for (const worker of workers) {
worker.terminate();
}
return solutions.find((solution) => !!solution) || null;
}
exports.solveChallengeWorkers = solveChallengeWorkers;
async function hashChallenge(salt, num, algorithm) {
return (0, helpers_js_1.ab2hex)(await crypto.subtle.digest(algorithm.toUpperCase(), helpers_js_1.encoder.encode(salt + num)));
}

6
cjs/dist/types.d.ts vendored
View File

@@ -2,6 +2,7 @@ export type Algorithm = 'SHA-1' | 'SHA-256' | 'SHA-512';
export interface Challenge {
algorithm: Algorithm;
challenge: string;
max?: number;
salt: string;
signature: string;
}
@@ -20,3 +21,8 @@ export interface Payload {
salt: string;
signature: string;
}
export interface Solution {
number: number;
took: number;
worker?: boolean;
}

1
cjs/dist/worker.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
export {};

19
cjs/dist/worker.js vendored Normal file
View File

@@ -0,0 +1,19 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const index_js_1 = require("./index.js");
let controller = undefined;
onmessage = async (message) => {
const { type, payload } = message.data;
if (type === 'abort') {
controller?.abort();
controller = undefined;
}
else if (type === 'work') {
const { alg, challenge, max, salt, start } = payload || {};
const result = (0, index_js_1.solveChallenge)(challenge, salt, alg, max, start);
controller = result.controller;
result.promise.then((solution) => {
self.postMessage(solution ? { ...solution, worker: true } : solution);
});
}
};

View File

@@ -37,8 +37,12 @@ Parameters:
- `options: ChallengeOptions`:
- `algorithm?: string`: Algorithm to use (`SHA-1`, `SHA-256`, `SHA-512`, default: `SHA-256`).
- `hmacKey: string` (required): Signature HMAC key.
- `maxNumber?: number` Optional maximum number for the random number generator (defaults to 1,000,000).
- `number?: number`: Optional number to use. If not provided, a random number will be generated.
- `salt?: string`: Optional salt string. If not provided, a random salt will be generated.
- `saltLength?: number` Optional maximum lenght of the random salt (in bytes, defaults to 12).
Returns: `Promise<Challenge>`
### `verifySolution(payload, hmacKey)`
@@ -49,6 +53,69 @@ Parameters:
- `payload: string | Payload`
- `hmacKey: string`
Returns: `Promise<boolean>`
### `solveChallenge(challenge, salt, algorithm?, max?, start?)`
Finds a solution to the given challenge.
Parameters:
- `challenge: string` (required): The challenge hash.
- `salt: string` (required): The challenge salt.
- `algorithm?: string`: Optional algorithm (default: `SHA-256`).
- `max?: string`: Optional `maxnumber` to iterate to (default: 1e6).
- `start?: string`: Optional starting number (default: 0).
Returns: `{ controller: AbortController, promise: Promise<Solution | null> }`
### `solveChallengeWorkers(workerScript, concurrency, challenge, salt, algorithm?, max?, start?)`
Finds a solution to the given challenge with [Web Workers](https://developer.mozilla.org/en-US/docs/Web/API/Worker/Worker) running concurrently.
Parameters:
- `workerScript: string` (required): The path or URL of the worker script.
- `concurrency: number` (required): The concurrency (number of workers).
- `challenge: string` (required): The challenge hash.
- `salt: string` (required): The challenge salt.
- `algorithm?: string`: Optional algorithm (default: `SHA-256`).
- `max?: string`: Optional `maxnumber` to iterate to (default: 1e6).
- `start?: string`: Optional starting number (default: 0).
Returns: `Promise<Solution | null>`
Usage with `altcha-lib/worker`:
```ts
import { solveChallengeWorkers } from 'altcha-lib';
const solution = await solveChallengeWorkers(
'altcha-lib/worker', // URL to
8, // spawn 8 workers
challenge,
salt,
);
```
## Benchmarks
```
> solveChallenge()
- n = 1,000............................... 317 ops/s ±2.63%
- n = 10,000.............................. 32 ops/s ±1.88%
- n = 100,000............................. 3 ops/s ±0.34%
- n = 500,000............................. 0 ops/s ±0.32%
> solveChallengeWorkers() (8 workers)
- n = 1,000............................... 66 ops/s ±3.44%
- n = 10,000.............................. 31 ops/s ±4.28%
- n = 100,000............................. 7 ops/s ±4.40%
- n = 500,000............................. 1 ops/s ±2.49%
```
Run with Bun on MacBook Pro M3-Pro. See [/benchmark](/benchmark/) folder for more details.
## License
MIT

4
deno_dist/crypto.ts Normal file
View File

@@ -0,0 +1,4 @@
if (!('crypto' in globalThis)) {
// eslint-disable-next-line @typescript-eslint/no-var-requires
globalThis.crypto = require('node:crypto').webcrypto;
}

View File

@@ -1,11 +1,7 @@
import './crypto.ts';
import type { Algorithm } from './types.ts';
const encoder = new TextEncoder();
if (!('crypto' in globalThis)) {
// @ts-ignore
globalThis.crypto = (await import('node:crypto')).webcrypto;
}
export const encoder = new TextEncoder();
export function ab2hex(ab: ArrayBuffer | Uint8Array) {
return [...new Uint8Array(ab)]

View File

@@ -1,25 +1,36 @@
import { ab2hex, hash, hmac, randomBytes, randomInt } from './helpers.ts';
import {
ab2hex,
encoder,
hash,
hmac,
randomBytes,
randomInt,
} from './helpers.ts';
import type {
Algorithm,
Challenge,
ChallengeOptions,
Payload,
Solution,
} from './types.ts';
const DEFAULT_MAX_NUMBER = 1e7;
const DEFAULT_MAX_NUMBER = 1e6;
const DEFAULT_SALT_LEN = 12;
const DEFAULT_ALG: Algorithm = 'SHA-256';
export async function createChallenge(
options: ChallengeOptions
): Promise<Challenge> {
const algorithm = options.algorithm || DEFAULT_ALG;
const salt = options.salt || ab2hex(randomBytes(12));
const number =
options.number === void 0 ? randomInt(DEFAULT_MAX_NUMBER) : options.number;
const max = options.maxNumber || DEFAULT_MAX_NUMBER;
const saltLength = options.saltLength || DEFAULT_SALT_LEN;
const salt = options.salt || ab2hex(randomBytes(saltLength));
const number = options.number === void 0 ? randomInt(max) : options.number;
const challenge = await hash(algorithm, salt + number);
return {
algorithm,
challenge,
max,
salt,
signature: await hmac(algorithm, challenge, options.hmacKey),
};
@@ -43,3 +54,105 @@ export async function verifySolution(
check.signature === payload.signature
);
}
export function solveChallenge(
challenge: string,
salt: string,
algorithm: string = 'SHA-256',
max: number = 1e6,
start: number = 0
): { promise: Promise<Solution | null>; controller: AbortController } {
const controller = new AbortController();
const promise = new Promise((resolve, reject) => {
const startTime = Date.now();
const next = (n: number) => {
if (controller.signal.aborted || n > max) {
resolve(null);
} else {
hashChallenge(salt, n, algorithm)
.then((t) => {
if (t === challenge) {
resolve({
number: n,
took: Date.now() - startTime,
});
} else {
next(n + 1);
}
})
.catch(reject);
}
};
next(start);
}) as Promise<Solution | null>;
return {
promise,
controller,
};
}
export async function solveChallengeWorkers(
workerScript: string | URL,
concurrency: number,
challenge: string,
salt: string,
algorithm: string = 'SHA-256',
max: number = 1e6,
startNumber: number = 0
) {
const workers: Worker[] = [];
if (concurrency < 1) {
throw new Error('Wrong number of workers configured.');
}
if (concurrency > 16) {
throw new Error('Too many workers. Max. 16 allowed workers.');
}
for (let i = 0; i < concurrency; i++) {
workers.push(
new Worker(workerScript, {
type: 'module',
})
);
}
const step = Math.ceil(max / concurrency);
const solutions = await Promise.all(
workers.map((worker, i) => {
const start = startNumber + i * step;
return new Promise((resolve) => {
worker.addEventListener('message', (message: MessageEvent) => {
if (message.data) {
for (const w of workers) {
if (w !== worker) {
w.postMessage({ type: 'abort' });
}
}
}
resolve(message.data);
});
worker.postMessage({
payload: {
algorithm,
challenge,
max: start + step,
salt,
start,
},
type: 'work',
});
}) as Promise<Solution | null>;
})
);
for (const worker of workers) {
worker.terminate();
}
return solutions.find((solution) => !!solution) || null;
}
async function hashChallenge(salt: string, num: number, algorithm: string) {
return ab2hex(
await crypto.subtle.digest(
algorithm.toUpperCase(),
encoder.encode(salt + num)
)
);
}

View File

@@ -3,6 +3,7 @@ export type Algorithm = 'SHA-1' | 'SHA-256' | 'SHA-512';
export interface Challenge {
algorithm: Algorithm;
challenge: string;
max?: number;
salt: string;
signature: string;
}
@@ -10,8 +11,10 @@ export interface Challenge {
export interface ChallengeOptions {
algorithm?: Algorithm;
hmacKey: string;
maxNumber?: number;
number?: number;
salt?: string;
saltLength?: number;
}
export interface Payload {
@@ -21,3 +24,9 @@ export interface Payload {
salt: string;
signature: string;
}
export interface Solution {
number: number;
took: number;
worker?: boolean;
}

20
deno_dist/worker.ts Normal file
View File

@@ -0,0 +1,20 @@
import { solveChallenge } from './index.ts';
let controller: AbortController | undefined = undefined;
onmessage = async (message) => {
const { type, payload } = message.data;
if (type === 'abort') {
controller?.abort();
controller = undefined;
} else if (type === 'work') {
const { alg, challenge, max, salt, start } = payload || {};
const result = solveChallenge(challenge, salt, alg, max, start);
controller = result.controller;
result.promise.then((solution) => {
self.postMessage(solution ? { ...solution, worker: true } : solution);
});
}
};
export {};

1
dist/crypto.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
export {};

5
dist/crypto.js vendored Normal file
View File

@@ -0,0 +1,5 @@
if (!('crypto' in globalThis)) {
// eslint-disable-next-line @typescript-eslint/no-var-requires
globalThis.crypto = require('node:crypto').webcrypto;
}
export {};

2
dist/helpers.d.ts vendored
View File

@@ -1,4 +1,6 @@
import './crypto.js';
import type { Algorithm } from './types.js';
export declare const encoder: TextEncoder;
export declare function ab2hex(ab: ArrayBuffer | Uint8Array): string;
export declare function hash(algorithm: Algorithm, str: string): Promise<string>;
export declare function hmac(algorithm: Algorithm, str: string, secret: string): Promise<string>;

7
dist/helpers.js vendored
View File

@@ -1,8 +1,5 @@
const encoder = new TextEncoder();
if (!('crypto' in globalThis)) {
// eslint-disable-next-line @typescript-eslint/no-var-requires
globalThis.crypto = require('node:crypto').webcrypto;
}
import './crypto.js';
export const encoder = new TextEncoder();
export function ab2hex(ab) {
return [...new Uint8Array(ab)]
.map((x) => x.toString(16).padStart(2, '0'))

7
dist/index.d.ts vendored
View File

@@ -1,3 +1,8 @@
import type { Challenge, ChallengeOptions, Payload } from './types.js';
import type { Challenge, ChallengeOptions, Payload, Solution } from './types.js';
export declare function createChallenge(options: ChallengeOptions): Promise<Challenge>;
export declare function verifySolution(payload: string | Payload, hmacKey: string): Promise<boolean>;
export declare function solveChallenge(challenge: string, salt: string, algorithm?: string, max?: number, start?: number): {
promise: Promise<Solution | null>;
controller: AbortController;
};
export declare function solveChallengeWorkers(workerScript: string | URL, concurrency: number, challenge: string, salt: string, algorithm?: string, max?: number, startNumber?: number): Promise<Solution | null>;

85
dist/index.js vendored
View File

@@ -1,17 +1,18 @@
import { ab2hex, hash, hmac, randomBytes, randomInt } from './helpers.js';
import { ab2hex, encoder, hash, hmac, randomBytes, randomInt, } from './helpers.js';
const DEFAULT_MAX_NUMBER = 1e6;
const DEFAULT_SALT_LEN = 12;
const DEFAULT_ALG = 'SHA-256';
export async function createChallenge(options) {
const algorithm = options.algorithm || DEFAULT_ALG;
const maxNumber = options.maxNumber || DEFAULT_MAX_NUMBER;
const max = options.maxNumber || DEFAULT_MAX_NUMBER;
const saltLength = options.saltLength || DEFAULT_SALT_LEN;
const salt = options.salt || ab2hex(randomBytes(saltLength));
const number = options.number === void 0 ? randomInt(maxNumber) : options.number;
const number = options.number === void 0 ? randomInt(max) : options.number;
const challenge = await hash(algorithm, salt + number);
return {
algorithm,
challenge,
max,
salt,
signature: await hmac(algorithm, challenge, options.hmacKey),
};
@@ -29,3 +30,81 @@ export async function verifySolution(payload, hmacKey) {
return (check.challenge === payload.challenge &&
check.signature === payload.signature);
}
export function solveChallenge(challenge, salt, algorithm = 'SHA-256', max = 1e6, start = 0) {
const controller = new AbortController();
const promise = new Promise((resolve, reject) => {
const startTime = Date.now();
const next = (n) => {
if (controller.signal.aborted || n > max) {
resolve(null);
}
else {
hashChallenge(salt, n, algorithm)
.then((t) => {
if (t === challenge) {
resolve({
number: n,
took: Date.now() - startTime,
});
}
else {
next(n + 1);
}
})
.catch(reject);
}
};
next(start);
});
return {
promise,
controller,
};
}
export async function solveChallengeWorkers(workerScript, concurrency, challenge, salt, algorithm = 'SHA-256', max = 1e6, startNumber = 0) {
const workers = [];
if (concurrency < 1) {
throw new Error('Wrong number of workers configured.');
}
if (concurrency > 16) {
throw new Error('Too many workers. Max. 16 allowed workers.');
}
for (let i = 0; i < concurrency; i++) {
workers.push(new Worker(workerScript, {
type: 'module',
}));
}
const step = Math.ceil(max / concurrency);
const solutions = await Promise.all(workers.map((worker, i) => {
const start = startNumber + i * step;
return new Promise((resolve) => {
worker.addEventListener('message', (message) => {
if (message.data) {
for (const w of workers) {
if (w !== worker) {
w.postMessage({ type: 'abort' });
}
}
}
resolve(message.data);
});
worker.postMessage({
payload: {
algorithm,
challenge,
max: start + step,
salt,
start,
},
type: 'work',
});
});
}));
for (const worker of workers) {
worker.terminate();
}
return solutions.find((solution) => !!solution) || null;
}
async function hashChallenge(salt, num, algorithm) {
return ab2hex(await crypto.subtle.digest(algorithm.toUpperCase(), encoder.encode(salt + num)));
}

6
dist/types.d.ts vendored
View File

@@ -2,6 +2,7 @@ export type Algorithm = 'SHA-1' | 'SHA-256' | 'SHA-512';
export interface Challenge {
algorithm: Algorithm;
challenge: string;
max?: number;
salt: string;
signature: string;
}
@@ -20,3 +21,8 @@ export interface Payload {
salt: string;
signature: string;
}
export interface Solution {
number: number;
took: number;
worker?: boolean;
}

1
dist/worker.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
export {};

17
dist/worker.js vendored Normal file
View File

@@ -0,0 +1,17 @@
import { solveChallenge } from './index.js';
let controller = undefined;
onmessage = async (message) => {
const { type, payload } = message.data;
if (type === 'abort') {
controller?.abort();
controller = undefined;
}
else if (type === 'work') {
const { alg, challenge, max, salt, start } = payload || {};
const result = solveChallenge(challenge, salt, alg, max, start);
controller = result.controller;
result.promise.then((solution) => {
self.postMessage(solution ? { ...solution, worker: true } : solution);
});
}
};

4
lib/crypto.ts Normal file
View File

@@ -0,0 +1,4 @@
if (!('crypto' in globalThis)) {
// eslint-disable-next-line @typescript-eslint/no-var-requires
globalThis.crypto = require('node:crypto').webcrypto;
}

View File

@@ -1,11 +1,7 @@
import './crypto.js';
import type { Algorithm } from './types.js';
const encoder = new TextEncoder();
if (!('crypto' in globalThis)) {
// eslint-disable-next-line @typescript-eslint/no-var-requires
globalThis.crypto = require('node:crypto').webcrypto;
}
export const encoder = new TextEncoder();
export function ab2hex(ab: ArrayBuffer | Uint8Array) {
return [...new Uint8Array(ab)]

View File

@@ -1,9 +1,17 @@
import { ab2hex, hash, hmac, randomBytes, randomInt } from './helpers.js';
import {
ab2hex,
encoder,
hash,
hmac,
randomBytes,
randomInt,
} from './helpers.js';
import type {
Algorithm,
Challenge,
ChallengeOptions,
Payload,
Solution,
} from './types.js';
const DEFAULT_MAX_NUMBER = 1e6;
@@ -14,15 +22,15 @@ export async function createChallenge(
options: ChallengeOptions
): Promise<Challenge> {
const algorithm = options.algorithm || DEFAULT_ALG;
const maxNumber = options.maxNumber || DEFAULT_MAX_NUMBER;
const max = options.maxNumber || DEFAULT_MAX_NUMBER;
const saltLength = options.saltLength || DEFAULT_SALT_LEN;
const salt = options.salt || ab2hex(randomBytes(saltLength));
const number =
options.number === void 0 ? randomInt(maxNumber) : options.number;
const number = options.number === void 0 ? randomInt(max) : options.number;
const challenge = await hash(algorithm, salt + number);
return {
algorithm,
challenge,
max,
salt,
signature: await hmac(algorithm, challenge, options.hmacKey),
};
@@ -46,3 +54,105 @@ export async function verifySolution(
check.signature === payload.signature
);
}
export function solveChallenge(
challenge: string,
salt: string,
algorithm: string = 'SHA-256',
max: number = 1e6,
start: number = 0
): { promise: Promise<Solution | null>; controller: AbortController } {
const controller = new AbortController();
const promise = new Promise((resolve, reject) => {
const startTime = Date.now();
const next = (n: number) => {
if (controller.signal.aborted || n > max) {
resolve(null);
} else {
hashChallenge(salt, n, algorithm)
.then((t) => {
if (t === challenge) {
resolve({
number: n,
took: Date.now() - startTime,
});
} else {
next(n + 1);
}
})
.catch(reject);
}
};
next(start);
}) as Promise<Solution | null>;
return {
promise,
controller,
};
}
export async function solveChallengeWorkers(
workerScript: string | URL,
concurrency: number,
challenge: string,
salt: string,
algorithm: string = 'SHA-256',
max: number = 1e6,
startNumber: number = 0
) {
const workers: Worker[] = [];
if (concurrency < 1) {
throw new Error('Wrong number of workers configured.');
}
if (concurrency > 16) {
throw new Error('Too many workers. Max. 16 allowed workers.');
}
for (let i = 0; i < concurrency; i++) {
workers.push(
new Worker(workerScript, {
type: 'module',
})
);
}
const step = Math.ceil(max / concurrency);
const solutions = await Promise.all(
workers.map((worker, i) => {
const start = startNumber + i * step;
return new Promise((resolve) => {
worker.addEventListener('message', (message: MessageEvent) => {
if (message.data) {
for (const w of workers) {
if (w !== worker) {
w.postMessage({ type: 'abort' });
}
}
}
resolve(message.data);
});
worker.postMessage({
payload: {
algorithm,
challenge,
max: start + step,
salt,
start,
},
type: 'work',
});
}) as Promise<Solution | null>;
})
);
for (const worker of workers) {
worker.terminate();
}
return solutions.find((solution) => !!solution) || null;
}
async function hashChallenge(salt: string, num: number, algorithm: string) {
return ab2hex(
await crypto.subtle.digest(
algorithm.toUpperCase(),
encoder.encode(salt + num)
)
);
}

View File

@@ -3,6 +3,7 @@ export type Algorithm = 'SHA-1' | 'SHA-256' | 'SHA-512';
export interface Challenge {
algorithm: Algorithm;
challenge: string;
max?: number;
salt: string;
signature: string;
}
@@ -23,3 +24,9 @@ export interface Payload {
salt: string;
signature: string;
}
export interface Solution {
number: number;
took: number;
worker?: boolean;
}

20
lib/worker.ts Normal file
View File

@@ -0,0 +1,20 @@
import { solveChallenge } from './index.js';
let controller: AbortController | undefined = undefined;
onmessage = async (message) => {
const { type, payload } = message.data;
if (type === 'abort') {
controller?.abort();
controller = undefined;
} else if (type === 'work') {
const { alg, challenge, max, salt, start } = payload || {};
const result = solveChallenge(challenge, salt, alg, max, start);
controller = result.controller;
result.promise.then((solution) => {
self.postMessage(solution ? { ...solution, worker: true } : solution);
});
}
};
export {};

590
package-lock.json generated
View File

@@ -1,18 +1,19 @@
{
"name": "altcha-lib",
"version": "0.1.1",
"version": "0.1.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "altcha-lib",
"version": "0.1.1",
"version": "0.1.2",
"license": "MIT",
"devDependencies": {
"@types/node": "^20.9.0",
"@typescript-eslint/eslint-plugin": "^6.21.0",
"denoify": "^1.6.9",
"eslint": "^8.56.0",
"husky": "^9.0.11",
"prettier": "^3.2.5",
"rimraf": "^5.0.5",
"ts-node": "^10.9.1",
@@ -953,9 +954,9 @@
}
},
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.10.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.10.0.tgz",
"integrity": "sha512-/MeDQmcD96nVoRumKUljsYOLqfv1YFJps+0pTrb2Z9Nl/w5qNUysMaWQsrd1mvAlNT4yza1iVyIu4Q4AgF6V3A==",
"version": "4.14.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.14.0.tgz",
"integrity": "sha512-jwXtxYbRt1V+CdQSy6Z+uZti7JF5irRKF8hlKfEnF/xJpcNGuuiZMBvuoYM+x9sr9iWGnzrlM0+9hvQ1kgkf1w==",
"cpu": [
"arm"
],
@@ -966,9 +967,9 @@
]
},
"node_modules/@rollup/rollup-android-arm64": {
"version": "4.10.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.10.0.tgz",
"integrity": "sha512-lvu0jK97mZDJdpZKDnZI93I0Om8lSDaiPx3OiCk0RXn3E8CMPJNS/wxjAvSJJzhhZpfjXsjLWL8LnS6qET4VNQ==",
"version": "4.14.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.14.0.tgz",
"integrity": "sha512-fI9nduZhCccjzlsA/OuAwtFGWocxA4gqXGTLvOyiF8d+8o0fZUeSztixkYjcGq1fGZY3Tkq4yRvHPFxU+jdZ9Q==",
"cpu": [
"arm64"
],
@@ -979,9 +980,9 @@
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.10.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.10.0.tgz",
"integrity": "sha512-uFpayx8I8tyOvDkD7X6n0PriDRWxcqEjqgtlxnUA/G9oS93ur9aZ8c8BEpzFmsed1TH5WZNG5IONB8IiW90TQg==",
"version": "4.14.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.14.0.tgz",
"integrity": "sha512-BcnSPRM76/cD2gQC+rQNGBN6GStBs2pl/FpweW8JYuz5J/IEa0Fr4AtrPv766DB/6b2MZ/AfSIOSGw3nEIP8SA==",
"cpu": [
"arm64"
],
@@ -992,9 +993,9 @@
]
},
"node_modules/@rollup/rollup-darwin-x64": {
"version": "4.10.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.10.0.tgz",
"integrity": "sha512-nIdCX03qFKoR/MwQegQBK+qZoSpO3LESurVAC6s6jazLA1Mpmgzo3Nj3H1vydXp/JM29bkCiuF7tDuToj4+U9Q==",
"version": "4.14.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.14.0.tgz",
"integrity": "sha512-LDyFB9GRolGN7XI6955aFeI3wCdCUszFWumWU0deHA8VpR3nWRrjG6GtGjBrQxQKFevnUTHKCfPR4IvrW3kCgQ==",
"cpu": [
"x64"
],
@@ -1005,9 +1006,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.10.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.10.0.tgz",
"integrity": "sha512-Fz7a+y5sYhYZMQFRkOyCs4PLhICAnxRX/GnWYReaAoruUzuRtcf+Qnw+T0CoAWbHCuz2gBUwmWnUgQ67fb3FYw==",
"version": "4.14.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.14.0.tgz",
"integrity": "sha512-ygrGVhQP47mRh0AAD0zl6QqCbNsf0eTo+vgwkY6LunBcg0f2Jv365GXlDUECIyoXp1kKwL5WW6rsO429DBY/bA==",
"cpu": [
"arm"
],
@@ -1018,9 +1019,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.10.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.10.0.tgz",
"integrity": "sha512-yPtF9jIix88orwfTi0lJiqINnlWo6p93MtZEoaehZnmCzEmLL0eqjA3eGVeyQhMtxdV+Mlsgfwhh0+M/k1/V7Q==",
"version": "4.14.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.14.0.tgz",
"integrity": "sha512-x+uJ6MAYRlHGe9wi4HQjxpaKHPM3d3JjqqCkeC5gpnnI6OWovLdXTpfa8trjxPLnWKyBsSi5kne+146GAxFt4A==",
"cpu": [
"arm64"
],
@@ -1031,9 +1032,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.10.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.10.0.tgz",
"integrity": "sha512-9GW9yA30ib+vfFiwjX+N7PnjTnCMiUffhWj4vkG4ukYv1kJ4T9gHNg8zw+ChsOccM27G9yXrEtMScf1LaCuoWQ==",
"version": "4.14.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.14.0.tgz",
"integrity": "sha512-nrRw8ZTQKg6+Lttwqo6a2VxR9tOroa2m91XbdQ2sUUzHoedXlsyvY1fN4xWdqz8PKmf4orDwejxXHjh7YBGUCA==",
"cpu": [
"arm64"
],
@@ -1043,10 +1044,23 @@
"linux"
]
},
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
"version": "4.14.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.14.0.tgz",
"integrity": "sha512-xV0d5jDb4aFu84XKr+lcUJ9y3qpIWhttO3Qev97z8DKLXR62LC3cXT/bMZXrjLF9X+P5oSmJTzAhqwUbY96PnA==",
"cpu": [
"ppc64le"
],
"dev": true,
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.10.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.10.0.tgz",
"integrity": "sha512-X1ES+V4bMq2ws5fF4zHornxebNxMXye0ZZjUrzOrf7UMx1d6wMQtfcchZ8SqUnQPPHdOyOLW6fTcUiFgHFadRA==",
"version": "4.14.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.14.0.tgz",
"integrity": "sha512-SDDhBQwZX6LPRoPYjAZWyL27LbcBo7WdBFWJi5PI9RPCzU8ijzkQn7tt8NXiXRiFMJCVpkuMkBf4OxSxVMizAw==",
"cpu": [
"riscv64"
],
@@ -1056,10 +1070,23 @@
"linux"
]
},
"node_modules/@rollup/rollup-linux-s390x-gnu": {
"version": "4.14.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.14.0.tgz",
"integrity": "sha512-RxB/qez8zIDshNJDufYlTT0ZTVut5eCpAZ3bdXDU9yTxBzui3KhbGjROK2OYTTor7alM7XBhssgoO3CZ0XD3qA==",
"cpu": [
"s390x"
],
"dev": true,
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.10.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.10.0.tgz",
"integrity": "sha512-w/5OpT2EnI/Xvypw4FIhV34jmNqU5PZjZue2l2Y3ty1Ootm3SqhI+AmfhlUYGBTd9JnpneZCDnt3uNOiOBkMyw==",
"version": "4.14.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.14.0.tgz",
"integrity": "sha512-C6y6z2eCNCfhZxT9u+jAM2Fup89ZjiG5pIzZIDycs1IwESviLxwkQcFRGLjnDrP+PT+v5i4YFvlcfAs+LnreXg==",
"cpu": [
"x64"
],
@@ -1070,9 +1097,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
"version": "4.10.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.10.0.tgz",
"integrity": "sha512-q/meftEe3QlwQiGYxD9rWwB21DoKQ9Q8wA40of/of6yGHhZuGfZO0c3WYkN9dNlopHlNT3mf5BPsUSxoPuVQaw==",
"version": "4.14.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.14.0.tgz",
"integrity": "sha512-i0QwbHYfnOMYsBEyjxcwGu5SMIi9sImDVjDg087hpzXqhBSosxkE7gyIYFHgfFl4mr7RrXksIBZ4DoLoP4FhJg==",
"cpu": [
"x64"
],
@@ -1083,9 +1110,9 @@
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.10.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.10.0.tgz",
"integrity": "sha512-NrR6667wlUfP0BHaEIKgYM/2va+Oj+RjZSASbBMnszM9k+1AmliRjHc3lJIiOehtSSjqYiO7R6KLNrWOX+YNSQ==",
"version": "4.14.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.14.0.tgz",
"integrity": "sha512-Fq52EYb0riNHLBTAcL0cun+rRwyZ10S9vKzhGKKgeD+XbwunszSY0rVMco5KbOsTlwovP2rTOkiII/fQ4ih/zQ==",
"cpu": [
"arm64"
],
@@ -1096,9 +1123,9 @@
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.10.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.10.0.tgz",
"integrity": "sha512-FV0Tpt84LPYDduIDcXvEC7HKtyXxdvhdAOvOeWMWbQNulxViH2O07QXkT/FffX4FqEI02jEbCJbr+YcuKdyyMg==",
"version": "4.14.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.14.0.tgz",
"integrity": "sha512-e/PBHxPdJ00O9p5Ui43+vixSgVf4NlLsmV6QneGERJ3lnjIua/kim6PRFe3iDueT1rQcgSkYP8ZBBXa/h4iPvw==",
"cpu": [
"ia32"
],
@@ -1109,9 +1136,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.10.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.10.0.tgz",
"integrity": "sha512-OZoJd+o5TaTSQeFFQ6WjFCiltiYVjIdsXxwu/XZ8qRpsvMQr4UsVrE5UyT9RIvsnuF47DqkJKhhVZ2Q9YW9IpQ==",
"version": "4.14.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.14.0.tgz",
"integrity": "sha512-aGg7iToJjdklmxlUlJh/PaPNa4PmqHfyRMLunbL3eaMO0gp656+q1zOKkpJ/CVe9CryJv6tAN1HDoR8cNGzkag==",
"cpu": [
"x64"
],
@@ -2585,6 +2612,21 @@
"node": ">=16.17.0"
}
},
"node_modules/husky": {
"version": "9.0.11",
"resolved": "https://registry.npmjs.org/husky/-/husky-9.0.11.tgz",
"integrity": "sha512-AB6lFlbwwyIqMdHYhwPe+kjOC3Oc5P3nThEoW/AaO2BX3vJDjWPFxYLxokUZOo6RNX20He3AaT8sESs9NJcmEw==",
"dev": true,
"bin": {
"husky": "bin.mjs"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/typicode"
}
},
"node_modules/ignore": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz",
@@ -3276,9 +3318,9 @@
}
},
"node_modules/postcss": {
"version": "8.4.35",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz",
"integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==",
"version": "8.4.38",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz",
"integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==",
"dev": true,
"funding": [
{
@@ -3297,7 +3339,7 @@
"dependencies": {
"nanoid": "^3.3.7",
"picocolors": "^1.0.0",
"source-map-js": "^1.0.2"
"source-map-js": "^1.2.0"
},
"engines": {
"node": "^10 || ^12 || >=14"
@@ -3432,9 +3474,9 @@
}
},
"node_modules/rollup": {
"version": "4.10.0",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.10.0.tgz",
"integrity": "sha512-t2v9G2AKxcQ8yrG+WGxctBes1AomT0M4ND7jTFBCVPXQ/WFTvNSefIrNSmLKhIKBrvN8SG+CZslimJcT3W2u2g==",
"version": "4.14.0",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.14.0.tgz",
"integrity": "sha512-Qe7w62TyawbDzB4yt32R0+AbIo6m1/sqO7UPzFS8Z/ksL5mrfhA0v4CavfdmFav3D+ub4QeAgsGEe84DoWe/nQ==",
"dev": true,
"dependencies": {
"@types/estree": "1.0.5"
@@ -3447,19 +3489,21 @@
"npm": ">=8.0.0"
},
"optionalDependencies": {
"@rollup/rollup-android-arm-eabi": "4.10.0",
"@rollup/rollup-android-arm64": "4.10.0",
"@rollup/rollup-darwin-arm64": "4.10.0",
"@rollup/rollup-darwin-x64": "4.10.0",
"@rollup/rollup-linux-arm-gnueabihf": "4.10.0",
"@rollup/rollup-linux-arm64-gnu": "4.10.0",
"@rollup/rollup-linux-arm64-musl": "4.10.0",
"@rollup/rollup-linux-riscv64-gnu": "4.10.0",
"@rollup/rollup-linux-x64-gnu": "4.10.0",
"@rollup/rollup-linux-x64-musl": "4.10.0",
"@rollup/rollup-win32-arm64-msvc": "4.10.0",
"@rollup/rollup-win32-ia32-msvc": "4.10.0",
"@rollup/rollup-win32-x64-msvc": "4.10.0",
"@rollup/rollup-android-arm-eabi": "4.14.0",
"@rollup/rollup-android-arm64": "4.14.0",
"@rollup/rollup-darwin-arm64": "4.14.0",
"@rollup/rollup-darwin-x64": "4.14.0",
"@rollup/rollup-linux-arm-gnueabihf": "4.14.0",
"@rollup/rollup-linux-arm64-gnu": "4.14.0",
"@rollup/rollup-linux-arm64-musl": "4.14.0",
"@rollup/rollup-linux-powerpc64le-gnu": "4.14.0",
"@rollup/rollup-linux-riscv64-gnu": "4.14.0",
"@rollup/rollup-linux-s390x-gnu": "4.14.0",
"@rollup/rollup-linux-x64-gnu": "4.14.0",
"@rollup/rollup-linux-x64-musl": "4.14.0",
"@rollup/rollup-win32-arm64-msvc": "4.14.0",
"@rollup/rollup-win32-ia32-msvc": "4.14.0",
"@rollup/rollup-win32-x64-msvc": "4.14.0",
"fsevents": "~2.3.2"
}
},
@@ -3577,9 +3621,9 @@
}
},
"node_modules/source-map-js": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz",
"integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==",
"dev": true,
"engines": {
"node": ">=0.10.0"
@@ -3955,14 +3999,14 @@
"dev": true
},
"node_modules/vite": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.1.1.tgz",
"integrity": "sha512-wclpAgY3F1tR7t9LL5CcHC41YPkQIpKUGeIuT8MdNwNZr6OqOTLs7JX5vIHAtzqLWXts0T+GDrh9pN2arneKqg==",
"version": "5.2.8",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.2.8.tgz",
"integrity": "sha512-OyZR+c1CE8yeHw5V5t59aXsUPPVTHMDjEZz8MgguLL/Q7NblxhZUlTu9xSPqlsUO/y+X7dlU05jdhvyycD55DA==",
"dev": true,
"dependencies": {
"esbuild": "^0.19.3",
"postcss": "^8.4.35",
"rollup": "^4.2.0"
"esbuild": "^0.20.1",
"postcss": "^8.4.38",
"rollup": "^4.13.0"
},
"bin": {
"vite": "bin/vite.js"
@@ -4031,6 +4075,412 @@
"url": "https://opencollective.com/vitest"
}
},
"node_modules/vite/node_modules/@esbuild/aix-ppc64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz",
"integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==",
"cpu": [
"ppc64"
],
"dev": true,
"optional": true,
"os": [
"aix"
],
"engines": {
"node": ">=12"
}
},
"node_modules/vite/node_modules/@esbuild/android-arm": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz",
"integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==",
"cpu": [
"arm"
],
"dev": true,
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=12"
}
},
"node_modules/vite/node_modules/@esbuild/android-arm64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz",
"integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=12"
}
},
"node_modules/vite/node_modules/@esbuild/android-x64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz",
"integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=12"
}
},
"node_modules/vite/node_modules/@esbuild/darwin-arm64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz",
"integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=12"
}
},
"node_modules/vite/node_modules/@esbuild/darwin-x64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz",
"integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=12"
}
},
"node_modules/vite/node_modules/@esbuild/freebsd-arm64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz",
"integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/vite/node_modules/@esbuild/freebsd-x64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz",
"integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/vite/node_modules/@esbuild/linux-arm": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz",
"integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==",
"cpu": [
"arm"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/vite/node_modules/@esbuild/linux-arm64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz",
"integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/vite/node_modules/@esbuild/linux-ia32": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz",
"integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==",
"cpu": [
"ia32"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/vite/node_modules/@esbuild/linux-loong64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz",
"integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==",
"cpu": [
"loong64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/vite/node_modules/@esbuild/linux-mips64el": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz",
"integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==",
"cpu": [
"mips64el"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/vite/node_modules/@esbuild/linux-ppc64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz",
"integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==",
"cpu": [
"ppc64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/vite/node_modules/@esbuild/linux-riscv64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz",
"integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==",
"cpu": [
"riscv64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/vite/node_modules/@esbuild/linux-s390x": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz",
"integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==",
"cpu": [
"s390x"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/vite/node_modules/@esbuild/linux-x64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz",
"integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/vite/node_modules/@esbuild/netbsd-x64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz",
"integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/vite/node_modules/@esbuild/openbsd-x64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz",
"integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/vite/node_modules/@esbuild/sunos-x64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz",
"integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"sunos"
],
"engines": {
"node": ">=12"
}
},
"node_modules/vite/node_modules/@esbuild/win32-arm64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz",
"integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
}
},
"node_modules/vite/node_modules/@esbuild/win32-ia32": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz",
"integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==",
"cpu": [
"ia32"
],
"dev": true,
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
}
},
"node_modules/vite/node_modules/@esbuild/win32-x64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz",
"integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
}
},
"node_modules/vite/node_modules/esbuild": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz",
"integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==",
"dev": true,
"hasInstallScript": true,
"bin": {
"esbuild": "bin/esbuild"
},
"engines": {
"node": ">=12"
},
"optionalDependencies": {
"@esbuild/aix-ppc64": "0.20.2",
"@esbuild/android-arm": "0.20.2",
"@esbuild/android-arm64": "0.20.2",
"@esbuild/android-x64": "0.20.2",
"@esbuild/darwin-arm64": "0.20.2",
"@esbuild/darwin-x64": "0.20.2",
"@esbuild/freebsd-arm64": "0.20.2",
"@esbuild/freebsd-x64": "0.20.2",
"@esbuild/linux-arm": "0.20.2",
"@esbuild/linux-arm64": "0.20.2",
"@esbuild/linux-ia32": "0.20.2",
"@esbuild/linux-loong64": "0.20.2",
"@esbuild/linux-mips64el": "0.20.2",
"@esbuild/linux-ppc64": "0.20.2",
"@esbuild/linux-riscv64": "0.20.2",
"@esbuild/linux-s390x": "0.20.2",
"@esbuild/linux-x64": "0.20.2",
"@esbuild/netbsd-x64": "0.20.2",
"@esbuild/openbsd-x64": "0.20.2",
"@esbuild/sunos-x64": "0.20.2",
"@esbuild/win32-arm64": "0.20.2",
"@esbuild/win32-ia32": "0.20.2",
"@esbuild/win32-x64": "0.20.2"
}
},
"node_modules/vitest": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/vitest/-/vitest-1.2.2.tgz",

View File

@@ -1,6 +1,6 @@
{
"name": "altcha-lib",
"version": "0.1.2",
"version": "0.1.3",
"description": "A library for creating and verifying ALTCHA challenges for Node.js, Bun and Deno.",
"author": "Daniel Regeci",
"license": "MIT",
@@ -19,8 +19,9 @@
"denoify": "rimraf deno_dist && denoify && find deno_dist/. -type f -exec sed -i '' -e 's/node:node:/node:/g' {} +",
"eslint": "eslint ./lib/**/*",
"format": "prettier --write './(lib|tests)/**/*'",
"test": "vitest",
"test:deno": "deno test tests/deno.ts"
"test": "vitest --run",
"test:deno": "deno test --allow-read tests/deno.ts",
"prepare": "husky"
},
"files": [
"cjs",
@@ -36,6 +37,12 @@
"./types": {
"types": "./dist/types.d.ts",
"import": "./dist/types.js"
},
"./worker": {
"types": "./dist/worker.d.ts",
"import": "./dist/worker.js",
"require": "./cjs/dist/worker.js",
"default": "./dist/worker.js"
}
},
"typesVersions": {
@@ -50,6 +57,7 @@
"@typescript-eslint/eslint-plugin": "^6.21.0",
"denoify": "^1.6.9",
"eslint": "^8.56.0",
"husky": "^9.0.11",
"prettier": "^3.2.5",
"rimraf": "^5.0.5",
"ts-node": "^10.9.1",

View File

@@ -1,5 +1,10 @@
import { describe, expect, it } from 'vitest';
import { createChallenge, verifySolution } from '../lib/index.js';
import {
createChallenge,
solveChallenge,
solveChallengeWorkers,
verifySolution,
} from '../lib/index.js';
import { Challenge } from '../lib/types.js';
describe('challenge', () => {
@@ -13,6 +18,7 @@ describe('challenge', () => {
expect(challenge).toEqual({
algorithm: 'SHA-256',
challenge: expect.any(String),
max: expect.any(Number),
salt: expect.any(String),
signature: expect.any(String),
} satisfies Challenge);
@@ -29,6 +35,7 @@ describe('challenge', () => {
expect(challenge).toEqual({
algorithm: 'SHA-1',
challenge: expect.any(String),
max: expect.any(Number),
salt: expect.any(String),
signature: expect.any(String),
} satisfies Challenge);
@@ -45,6 +52,7 @@ describe('challenge', () => {
expect(challenge).toEqual({
algorithm: 'SHA-512',
challenge: expect.any(String),
max: expect.any(Number),
salt: expect.any(String),
signature: expect.any(String),
} satisfies Challenge);
@@ -168,4 +176,39 @@ describe('challenge', () => {
expect(ok).toEqual(false);
});
});
describe('solveChallenge', () => {
it('should solve challenge', async () => {
const number = 100;
const challenge = await createChallenge({
number,
hmacKey,
});
const { promise } = solveChallenge(
challenge.challenge,
challenge.salt,
challenge.algorithm
);
const result = await promise;
expect(result?.number).toEqual(number);
});
});
describe.skipIf(!('Worker' in globalThis))('solveChallengeWorkers', () => {
it('should solve challenge using workers', async () => {
const number = 100;
const challenge = await createChallenge({
number,
hmacKey,
});
const result = await solveChallengeWorkers(
'./lib/worker.ts',
4,
challenge.challenge,
challenge.salt,
challenge.algorithm
);
expect(result?.number).toEqual(number);
});
});
});

View File

@@ -1,5 +1,10 @@
import { assertEquals } from 'https://deno.land/std@0.213.0/assert/mod.ts';
import { createChallenge, verifySolution } from '../deno_dist/index.ts';
import {
createChallenge,
verifySolution,
solveChallenge,
solveChallengeWorkers,
} from '../deno_dist/index.ts';
const hmacKey = 'test';
@@ -35,3 +40,33 @@ Deno.test('verifySolution()', async (t) => {
assertEquals(ok, true);
});
});
Deno.test('solveChallenge()', async (t) => {
await t.step('should solve challenge', async () => {
const number = 100;
const challenge = await createChallenge({
hmacKey,
number,
});
const result = await solveChallenge(challenge.challenge, challenge.salt)
.promise;
assertEquals(result?.number, number);
});
});
Deno.test('solveChallengeWorkers()', async (t) => {
await t.step('should solve challenge', async () => {
const number = 100;
const challenge = await createChallenge({
hmacKey,
number,
});
const result = await solveChallengeWorkers(
new URL('../deno_dist/worker.ts', import.meta.url),
8,
challenge.challenge,
challenge.salt
);
assertEquals(result?.number, number);
});
});