altcha-lib 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -14
- package/cjs/dist/index.d.ts +5 -1
- package/cjs/dist/index.js +42 -26
- package/cjs/dist/types.d.ts +2 -0
- package/dist/index.d.ts +5 -1
- package/dist/index.js +40 -25
- package/dist/types.d.ts +2 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,11 +4,12 @@ ALTCHA JS Library is a lightweight, zero-dependency library designed for creatin
|
|
|
4
4
|
|
|
5
5
|
## Compatibility
|
|
6
6
|
|
|
7
|
-
This library utilizes [Web Crypto](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto)
|
|
7
|
+
This library utilizes [Web Crypto](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto).
|
|
8
8
|
|
|
9
9
|
- Node.js 16+
|
|
10
10
|
- Bun 1+
|
|
11
11
|
- Deno 1+
|
|
12
|
+
- All modern browsers
|
|
12
13
|
|
|
13
14
|
## Usage
|
|
14
15
|
|
|
@@ -37,15 +38,27 @@ Parameters:
|
|
|
37
38
|
|
|
38
39
|
- `options: ChallengeOptions`:
|
|
39
40
|
- `algorithm?: string`: Algorithm to use (`SHA-1`, `SHA-256`, `SHA-512`, default: `SHA-256`).
|
|
41
|
+
- `expires?: Date`: Optional `expires` time (as `Date` set into the future date).
|
|
40
42
|
- `hmacKey: string` (required): Signature HMAC key.
|
|
41
|
-
- `
|
|
43
|
+
- `maxnumber?: number`: Optional maximum number for the random number generator (defaults to 1,000,000).
|
|
42
44
|
- `number?: number`: Optional number to use. If not provided, a random number will be generated.
|
|
45
|
+
- `params?: Record<string, string>`: Optional parameters to be added to the salt as URL-encoded query string. Use `extractParams()` to read them.
|
|
43
46
|
- `salt?: string`: Optional salt string. If not provided, a random salt will be generated.
|
|
44
|
-
- `saltLength?: number
|
|
47
|
+
- `saltLength?: number`: Optional maximum lenght of the random salt (in bytes, defaults to 12).
|
|
45
48
|
|
|
46
49
|
Returns: `Promise<Challenge>`
|
|
47
50
|
|
|
48
|
-
### `
|
|
51
|
+
### `extractParams(payload)`
|
|
52
|
+
|
|
53
|
+
Extracts optional parameters from the challenge or payload.
|
|
54
|
+
|
|
55
|
+
Parameters:
|
|
56
|
+
|
|
57
|
+
- `payload: string | Payload | Challenge`
|
|
58
|
+
|
|
59
|
+
Returns: `Record<string, string>`
|
|
60
|
+
|
|
61
|
+
### `verifySolution(payload, hmacKey, checkExpires = true)`
|
|
49
62
|
|
|
50
63
|
Verifies an ALTCHA solution. The payload can be a Base64-encoded JSON payload (as submitted by the widget) or an object.
|
|
51
64
|
|
|
@@ -53,6 +66,7 @@ Parameters:
|
|
|
53
66
|
|
|
54
67
|
- `payload: string | Payload`
|
|
55
68
|
- `hmacKey: string`
|
|
69
|
+
- `checkExpires: boolean = true`: Whether to perform a check on the optional `expires` parameter. Will return `false` if challenge expired.
|
|
56
70
|
|
|
57
71
|
Returns: `Promise<boolean>`
|
|
58
72
|
|
|
@@ -65,7 +79,7 @@ Parameters:
|
|
|
65
79
|
- `challenge: string` (required): The challenge hash.
|
|
66
80
|
- `salt: string` (required): The challenge salt.
|
|
67
81
|
- `algorithm?: string`: Optional algorithm (default: `SHA-256`).
|
|
68
|
-
- `
|
|
82
|
+
- `maxnumber?: string`: Optional `maxnumber` to iterate to (default: 1e6).
|
|
69
83
|
- `start?: string`: Optional starting number (default: 0).
|
|
70
84
|
|
|
71
85
|
Returns: `{ controller: AbortController, promise: Promise<Solution | null> }`
|
|
@@ -92,7 +106,7 @@ Parameters:
|
|
|
92
106
|
- `challenge: string` (required): The challenge hash.
|
|
93
107
|
- `salt: string` (required): The challenge salt.
|
|
94
108
|
- `algorithm?: string`: Optional algorithm (default: `SHA-256`).
|
|
95
|
-
- `
|
|
109
|
+
- `maxnumber?: string`: Optional `maxnumber` to iterate to (default: 1e6).
|
|
96
110
|
- `start?: string`: Optional starting number (default: 0).
|
|
97
111
|
|
|
98
112
|
Returns: `Promise<Solution | null>`
|
|
@@ -114,16 +128,18 @@ const solution = await solveChallengeWorkers(
|
|
|
114
128
|
|
|
115
129
|
```
|
|
116
130
|
> solveChallenge()
|
|
117
|
-
- n = 1,000...............................
|
|
118
|
-
- n = 10,000..............................
|
|
119
|
-
- n =
|
|
120
|
-
- n =
|
|
131
|
+
- n = 1,000............................... 312 ops/s ±2.90%
|
|
132
|
+
- n = 10,000.............................. 31 ops/s ±1.50%
|
|
133
|
+
- n = 50,000.............................. 6 ops/s ±0.82%
|
|
134
|
+
- n = 100,000............................. 3 ops/s ±0.37%
|
|
135
|
+
- n = 500,000............................. 0 ops/s ±0.31%
|
|
121
136
|
|
|
122
137
|
> solveChallengeWorkers() (8 workers)
|
|
123
|
-
- n = 1,000...............................
|
|
124
|
-
- n = 10,000.............................. 31 ops/s ±
|
|
125
|
-
- n =
|
|
126
|
-
- n =
|
|
138
|
+
- n = 1,000............................... 62 ops/s ±3.99%
|
|
139
|
+
- n = 10,000.............................. 31 ops/s ±6.83%
|
|
140
|
+
- n = 50,000.............................. 11 ops/s ±4.00%
|
|
141
|
+
- n = 100,000............................. 7 ops/s ±2.32%
|
|
142
|
+
- n = 500,000............................. 1 ops/s ±1.89%
|
|
127
143
|
```
|
|
128
144
|
|
|
129
145
|
Run with Bun on MacBook Pro M3-Pro. See [/benchmark](/benchmark/) folder for more details.
|
package/cjs/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import type { Challenge, ChallengeOptions, Payload, ServerSignaturePayload, ServerSignatureVerificationData, Solution } from './types.js';
|
|
2
2
|
export declare function createChallenge(options: ChallengeOptions): Promise<Challenge>;
|
|
3
|
-
export declare function
|
|
3
|
+
export declare function extractParams(payload: string | Payload | Challenge): {
|
|
4
|
+
[k: string]: string;
|
|
5
|
+
};
|
|
6
|
+
export declare function verifySolution(payload: string | Payload, hmacKey: string, checkExpires?: boolean): Promise<boolean>;
|
|
4
7
|
export declare function verifyServerSignature(payload: string | ServerSignaturePayload, hmacKey: string): Promise<{
|
|
5
8
|
verificationData: ServerSignatureVerificationData | null;
|
|
6
9
|
verified: boolean | null;
|
|
@@ -12,6 +15,7 @@ export declare function solveChallenge(challenge: string, salt: string, algorith
|
|
|
12
15
|
export declare function solveChallengeWorkers(workerScript: string | URL | (() => Worker), concurrency: number, challenge: string, salt: string, algorithm?: string, max?: number, startNumber?: number): Promise<Solution | null>;
|
|
13
16
|
declare const _default: {
|
|
14
17
|
createChallenge: typeof createChallenge;
|
|
18
|
+
extractParams: typeof extractParams;
|
|
15
19
|
solveChallenge: typeof solveChallenge;
|
|
16
20
|
solveChallengeWorkers: typeof solveChallengeWorkers;
|
|
17
21
|
verifyServerSignature: typeof verifyServerSignature;
|
package/cjs/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.solveChallengeWorkers = exports.solveChallenge = exports.verifyServerSignature = exports.verifySolution = exports.createChallenge = void 0;
|
|
3
|
+
exports.solveChallengeWorkers = exports.solveChallenge = exports.verifyServerSignature = exports.verifySolution = exports.extractParams = exports.createChallenge = void 0;
|
|
4
4
|
const helpers_js_1 = require("./helpers.js");
|
|
5
5
|
const DEFAULT_MAX_NUMBER = 1e6;
|
|
6
6
|
const DEFAULT_SALT_LEN = 12;
|
|
@@ -9,7 +9,15 @@ async function createChallenge(options) {
|
|
|
9
9
|
const algorithm = options.algorithm || DEFAULT_ALG;
|
|
10
10
|
const maxnumber = options.maxnumber || options.maxNumber || DEFAULT_MAX_NUMBER;
|
|
11
11
|
const saltLength = options.saltLength || DEFAULT_SALT_LEN;
|
|
12
|
-
const
|
|
12
|
+
const params = new URLSearchParams(options.params);
|
|
13
|
+
if (options.expires) {
|
|
14
|
+
params.set('expires', String(Math.floor(options.expires.getTime() / 1000)));
|
|
15
|
+
}
|
|
16
|
+
let salt = options.salt || (0, helpers_js_1.ab2hex)((0, helpers_js_1.randomBytes)(saltLength));
|
|
17
|
+
// params.size doesn't work with Node 16
|
|
18
|
+
if (Object.keys(Object.fromEntries(params)).length) {
|
|
19
|
+
salt = salt + '?' + params.toString();
|
|
20
|
+
}
|
|
13
21
|
const number = options.number === void 0 ? (0, helpers_js_1.randomInt)(maxnumber) : options.number;
|
|
14
22
|
const challenge = await (0, helpers_js_1.hashHex)(algorithm, salt + number);
|
|
15
23
|
return {
|
|
@@ -21,10 +29,25 @@ async function createChallenge(options) {
|
|
|
21
29
|
};
|
|
22
30
|
}
|
|
23
31
|
exports.createChallenge = createChallenge;
|
|
24
|
-
|
|
32
|
+
function extractParams(payload) {
|
|
25
33
|
if (typeof payload === 'string') {
|
|
26
34
|
payload = JSON.parse(atob(payload));
|
|
27
35
|
}
|
|
36
|
+
return Object.fromEntries(new URLSearchParams(payload.salt.split('?')?.[1] || ''));
|
|
37
|
+
}
|
|
38
|
+
exports.extractParams = extractParams;
|
|
39
|
+
async function verifySolution(payload, hmacKey, checkExpires = true) {
|
|
40
|
+
if (typeof payload === 'string') {
|
|
41
|
+
payload = JSON.parse(atob(payload));
|
|
42
|
+
}
|
|
43
|
+
const params = extractParams(payload);
|
|
44
|
+
const expires = params.expires || params.expire;
|
|
45
|
+
if (checkExpires && expires) {
|
|
46
|
+
const date = new Date(parseInt(expires, 10) * 1000);
|
|
47
|
+
if (!isNaN(date.getTime()) && date.getTime() < Date.now()) {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
28
51
|
const check = await createChallenge({
|
|
29
52
|
algorithm: payload.algorithm,
|
|
30
53
|
hmacKey,
|
|
@@ -70,32 +93,24 @@ async function verifyServerSignature(payload, hmacKey) {
|
|
|
70
93
|
exports.verifyServerSignature = verifyServerSignature;
|
|
71
94
|
function solveChallenge(challenge, salt, algorithm = 'SHA-256', max = 1e6, start = 0) {
|
|
72
95
|
const controller = new AbortController();
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
if (controller.signal.aborted
|
|
77
|
-
|
|
96
|
+
const startTime = Date.now();
|
|
97
|
+
const fn = async () => {
|
|
98
|
+
for (let n = start; n <= max; n += 1) {
|
|
99
|
+
if (controller.signal.aborted) {
|
|
100
|
+
return null;
|
|
78
101
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
took: Date.now() - startTime,
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
else {
|
|
89
|
-
next(n + 1);
|
|
90
|
-
}
|
|
91
|
-
})
|
|
92
|
-
.catch(reject);
|
|
102
|
+
const t = await (0, helpers_js_1.hashHex)(algorithm, salt + n);
|
|
103
|
+
if (t === challenge) {
|
|
104
|
+
return {
|
|
105
|
+
number: n,
|
|
106
|
+
took: Date.now() - startTime,
|
|
107
|
+
};
|
|
93
108
|
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
}
|
|
109
|
+
}
|
|
110
|
+
return null;
|
|
111
|
+
};
|
|
97
112
|
return {
|
|
98
|
-
promise,
|
|
113
|
+
promise: fn(),
|
|
99
114
|
controller,
|
|
100
115
|
};
|
|
101
116
|
}
|
|
@@ -152,6 +167,7 @@ async function solveChallengeWorkers(workerScript, concurrency, challenge, salt,
|
|
|
152
167
|
exports.solveChallengeWorkers = solveChallengeWorkers;
|
|
153
168
|
exports.default = {
|
|
154
169
|
createChallenge,
|
|
170
|
+
extractParams,
|
|
155
171
|
solveChallenge,
|
|
156
172
|
solveChallengeWorkers,
|
|
157
173
|
verifyServerSignature,
|
package/cjs/dist/types.d.ts
CHANGED
|
@@ -8,10 +8,12 @@ export interface Challenge {
|
|
|
8
8
|
}
|
|
9
9
|
export interface ChallengeOptions {
|
|
10
10
|
algorithm?: Algorithm;
|
|
11
|
+
expires?: Date;
|
|
11
12
|
hmacKey: string;
|
|
12
13
|
maxnumber?: number;
|
|
13
14
|
maxNumber?: number;
|
|
14
15
|
number?: number;
|
|
16
|
+
params?: Record<string, string>;
|
|
15
17
|
salt?: string;
|
|
16
18
|
saltLength?: number;
|
|
17
19
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import type { Challenge, ChallengeOptions, Payload, ServerSignaturePayload, ServerSignatureVerificationData, Solution } from './types.js';
|
|
2
2
|
export declare function createChallenge(options: ChallengeOptions): Promise<Challenge>;
|
|
3
|
-
export declare function
|
|
3
|
+
export declare function extractParams(payload: string | Payload | Challenge): {
|
|
4
|
+
[k: string]: string;
|
|
5
|
+
};
|
|
6
|
+
export declare function verifySolution(payload: string | Payload, hmacKey: string, checkExpires?: boolean): Promise<boolean>;
|
|
4
7
|
export declare function verifyServerSignature(payload: string | ServerSignaturePayload, hmacKey: string): Promise<{
|
|
5
8
|
verificationData: ServerSignatureVerificationData | null;
|
|
6
9
|
verified: boolean | null;
|
|
@@ -12,6 +15,7 @@ export declare function solveChallenge(challenge: string, salt: string, algorith
|
|
|
12
15
|
export declare function solveChallengeWorkers(workerScript: string | URL | (() => Worker), concurrency: number, challenge: string, salt: string, algorithm?: string, max?: number, startNumber?: number): Promise<Solution | null>;
|
|
13
16
|
declare const _default: {
|
|
14
17
|
createChallenge: typeof createChallenge;
|
|
18
|
+
extractParams: typeof extractParams;
|
|
15
19
|
solveChallenge: typeof solveChallenge;
|
|
16
20
|
solveChallengeWorkers: typeof solveChallengeWorkers;
|
|
17
21
|
verifyServerSignature: typeof verifyServerSignature;
|
package/dist/index.js
CHANGED
|
@@ -6,7 +6,15 @@ export async function createChallenge(options) {
|
|
|
6
6
|
const algorithm = options.algorithm || DEFAULT_ALG;
|
|
7
7
|
const maxnumber = options.maxnumber || options.maxNumber || DEFAULT_MAX_NUMBER;
|
|
8
8
|
const saltLength = options.saltLength || DEFAULT_SALT_LEN;
|
|
9
|
-
const
|
|
9
|
+
const params = new URLSearchParams(options.params);
|
|
10
|
+
if (options.expires) {
|
|
11
|
+
params.set('expires', String(Math.floor(options.expires.getTime() / 1000)));
|
|
12
|
+
}
|
|
13
|
+
let salt = options.salt || ab2hex(randomBytes(saltLength));
|
|
14
|
+
// params.size doesn't work with Node 16
|
|
15
|
+
if (Object.keys(Object.fromEntries(params)).length) {
|
|
16
|
+
salt = salt + '?' + params.toString();
|
|
17
|
+
}
|
|
10
18
|
const number = options.number === void 0 ? randomInt(maxnumber) : options.number;
|
|
11
19
|
const challenge = await hashHex(algorithm, salt + number);
|
|
12
20
|
return {
|
|
@@ -17,10 +25,24 @@ export async function createChallenge(options) {
|
|
|
17
25
|
signature: await hmacHex(algorithm, challenge, options.hmacKey),
|
|
18
26
|
};
|
|
19
27
|
}
|
|
20
|
-
export
|
|
28
|
+
export function extractParams(payload) {
|
|
21
29
|
if (typeof payload === 'string') {
|
|
22
30
|
payload = JSON.parse(atob(payload));
|
|
23
31
|
}
|
|
32
|
+
return Object.fromEntries(new URLSearchParams(payload.salt.split('?')?.[1] || ''));
|
|
33
|
+
}
|
|
34
|
+
export async function verifySolution(payload, hmacKey, checkExpires = true) {
|
|
35
|
+
if (typeof payload === 'string') {
|
|
36
|
+
payload = JSON.parse(atob(payload));
|
|
37
|
+
}
|
|
38
|
+
const params = extractParams(payload);
|
|
39
|
+
const expires = params.expires || params.expire;
|
|
40
|
+
if (checkExpires && expires) {
|
|
41
|
+
const date = new Date(parseInt(expires, 10) * 1000);
|
|
42
|
+
if (!isNaN(date.getTime()) && date.getTime() < Date.now()) {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
24
46
|
const check = await createChallenge({
|
|
25
47
|
algorithm: payload.algorithm,
|
|
26
48
|
hmacKey,
|
|
@@ -64,32 +86,24 @@ export async function verifyServerSignature(payload, hmacKey) {
|
|
|
64
86
|
}
|
|
65
87
|
export function solveChallenge(challenge, salt, algorithm = 'SHA-256', max = 1e6, start = 0) {
|
|
66
88
|
const controller = new AbortController();
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
if (controller.signal.aborted
|
|
71
|
-
|
|
89
|
+
const startTime = Date.now();
|
|
90
|
+
const fn = async () => {
|
|
91
|
+
for (let n = start; n <= max; n += 1) {
|
|
92
|
+
if (controller.signal.aborted) {
|
|
93
|
+
return null;
|
|
72
94
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
took: Date.now() - startTime,
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
else {
|
|
83
|
-
next(n + 1);
|
|
84
|
-
}
|
|
85
|
-
})
|
|
86
|
-
.catch(reject);
|
|
95
|
+
const t = await hashHex(algorithm, salt + n);
|
|
96
|
+
if (t === challenge) {
|
|
97
|
+
return {
|
|
98
|
+
number: n,
|
|
99
|
+
took: Date.now() - startTime,
|
|
100
|
+
};
|
|
87
101
|
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
}
|
|
102
|
+
}
|
|
103
|
+
return null;
|
|
104
|
+
};
|
|
91
105
|
return {
|
|
92
|
-
promise,
|
|
106
|
+
promise: fn(),
|
|
93
107
|
controller,
|
|
94
108
|
};
|
|
95
109
|
}
|
|
@@ -144,6 +158,7 @@ export async function solveChallengeWorkers(workerScript, concurrency, challenge
|
|
|
144
158
|
}
|
|
145
159
|
export default {
|
|
146
160
|
createChallenge,
|
|
161
|
+
extractParams,
|
|
147
162
|
solveChallenge,
|
|
148
163
|
solveChallengeWorkers,
|
|
149
164
|
verifyServerSignature,
|
package/dist/types.d.ts
CHANGED
|
@@ -8,10 +8,12 @@ export interface Challenge {
|
|
|
8
8
|
}
|
|
9
9
|
export interface ChallengeOptions {
|
|
10
10
|
algorithm?: Algorithm;
|
|
11
|
+
expires?: Date;
|
|
11
12
|
hmacKey: string;
|
|
12
13
|
maxnumber?: number;
|
|
13
14
|
maxNumber?: number;
|
|
14
15
|
number?: number;
|
|
16
|
+
params?: Record<string, string>;
|
|
15
17
|
salt?: string;
|
|
16
18
|
saltLength?: number;
|
|
17
19
|
}
|