Skip to content

Commit

Permalink
0.1.3
Browse files Browse the repository at this point in the history
  • Loading branch information
ovx committed Apr 6, 2024
1 parent e3ca2a5 commit 281380a
Show file tree
Hide file tree
Showing 40 changed files with 1,451 additions and 118 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/node_modules
/benchmark/node_modules
.TODO
.DS_Store
6 changes: 6 additions & 0 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
npm test
npm run build
npm run denoify
git add deno_dist
git add dist
git add cjs
65 changes: 65 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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 changes: 94 additions & 0 deletions benchmark/bench.ts
Original file line number Diff line number Diff line change
@@ -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 changes: 27 additions & 0 deletions benchmark/helpers.ts
Original file line number Diff line number Diff line change
@@ -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 changes: 18 additions & 0 deletions benchmark/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions benchmark/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"type": "module",
"scripts": {
"bench": "npx tsx bench.ts"
},
"devDependencies": {
"tinybench": "^2.6.0"
}
}
1 change: 1 addition & 0 deletions cjs/dist/crypto.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export {};
6 changes: 6 additions & 0 deletions cjs/dist/crypto.js
Original file line number Diff line number Diff line change
@@ -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;
}
2 changes: 2 additions & 0 deletions cjs/dist/helpers.d.ts
Original file line number Diff line number Diff line change
@@ -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>;
Expand Down
15 changes: 6 additions & 9 deletions cjs/dist/helpers.js
Original file line number Diff line number Diff line change
@@ -1,27 +1,24 @@
"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'))
.join('');
}
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) {
Expand Down
7 changes: 6 additions & 1 deletion cjs/dist/index.d.ts
Original file line number Diff line number Diff line change
@@ -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>;
Loading

0 comments on commit 281380a

Please sign in to comment.