ribaunt 0.2.3 → 0.2.4
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 +53 -8
- package/demo/lib/ribaunt-solver.wasm +0 -0
- package/demo/lib/solver-worker.js +174 -0
- package/demo/lib/solver-worker.js.map +1 -0
- package/demo/lib/solver.js +135 -0
- package/demo/lib/solver.js.map +1 -0
- package/demo/lib/wasm-solver.js +296 -0
- package/demo/lib/wasm-solver.js.map +1 -0
- package/demo/lib/widget-browser.js +9 -0
- package/demo/lib/widget-browser.js.map +1 -0
- package/demo/lib/widget.js +942 -0
- package/demo/lib/widget.js.map +1 -0
- package/demo/lib/worker-client.js +128 -0
- package/demo/lib/worker-client.js.map +1 -0
- package/dist/cjs/index.d.ts +24 -1
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +160 -13
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/risk.d.ts +124 -0
- package/dist/cjs/risk.d.ts.map +1 -0
- package/dist/cjs/risk.js +213 -0
- package/dist/cjs/risk.js.map +1 -0
- package/dist/index.d.ts +24 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +158 -12
- package/dist/index.js.map +1 -1
- package/dist/ribaunt-solver.wasm +0 -0
- package/dist/risk.d.ts +124 -0
- package/dist/risk.d.ts.map +1 -0
- package/dist/risk.js +202 -0
- package/dist/risk.js.map +1 -0
- package/dist/solver-worker.d.ts.map +1 -1
- package/dist/solver-worker.js +135 -5
- package/dist/solver-worker.js.map +1 -1
- package/dist/solver.d.ts +1 -0
- package/dist/solver.d.ts.map +1 -1
- package/dist/solver.js +38 -3
- package/dist/solver.js.map +1 -1
- package/dist/wasm-solver.d.ts +29 -0
- package/dist/wasm-solver.d.ts.map +1 -0
- package/dist/wasm-solver.js +296 -0
- package/dist/wasm-solver.js.map +1 -0
- package/dist/widget-browser.d.ts +3 -2
- package/dist/widget-browser.d.ts.map +1 -1
- package/dist/widget-browser.js +1 -1
- package/dist/widget-browser.js.map +1 -1
- package/dist/widget-react.d.ts +3 -1
- package/dist/widget-react.d.ts.map +1 -1
- package/dist/widget-react.js +99 -15
- package/dist/widget-react.js.map +1 -1
- package/dist/widget.d.ts +35 -15
- package/dist/widget.d.ts.map +1 -1
- package/dist/widget.js +61 -10
- package/dist/widget.js.map +1 -1
- package/dist/worker-client.d.ts +7 -1
- package/dist/worker-client.d.ts.map +1 -1
- package/dist/worker-client.js +32 -5
- package/dist/worker-client.js.map +1 -1
- package/package.json +19 -7
- package/context7.json +0 -4
package/README.md
CHANGED
|
@@ -16,6 +16,7 @@ Ribaunt is a stateless proof-of-work CAPTCHA library for Node.js and modern brow
|
|
|
16
16
|
- Browser widget for plain HTML apps
|
|
17
17
|
- React and Next.js-friendly wrapper via `ribaunt/widget-react`
|
|
18
18
|
- Server helpers for creating, solving, and verifying PoW challenges
|
|
19
|
+
- Programmable risk engine (`assess()`) with caller-supplied signals, pluggable scorers, and `allow`/`challenge`/`block` policy
|
|
19
20
|
- Default process-local replay protection with support for remote replay stores
|
|
20
21
|
- CSS custom properties for theming
|
|
21
22
|
- TypeScript types included
|
|
@@ -151,6 +152,8 @@ const challenges = await createChallenge(5, 4, 120);
|
|
|
151
152
|
|
|
152
153
|
Validate user- or config-controlled values before passing them to `createChallenge()`. Invalid values throw.
|
|
153
154
|
|
|
155
|
+
> **API note:** the options object form defaults to a single challenge (`amount: 1`) unless you pass `amount`; only the positional form above defaults to `4`.
|
|
156
|
+
|
|
154
157
|
### `createChallenge({ difficulty: "auto", ... })`
|
|
155
158
|
|
|
156
159
|
Creates adaptive challenge tokens from a server-bounded calibration hint. Calibration is untrusted and raise-only: it can increase work for fast clients, but it never lowers the server baseline.
|
|
@@ -204,9 +207,41 @@ const workload = selectWorkload({
|
|
|
204
207
|
minDifficulty: 3,
|
|
205
208
|
maxDifficulty: 6,
|
|
206
209
|
});
|
|
207
|
-
// { difficulty:
|
|
210
|
+
// { difficulty: 3, amount: 5, estimatedAttempts: 20480 }
|
|
208
211
|
```
|
|
209
212
|
|
|
213
|
+
### `assess(options)` — Risk Engine
|
|
214
|
+
|
|
215
|
+
Stateless, caller-driven risk assessment. All signals are caller-supplied (untrusted). Returns a bounded heuristic risk score (0–100, not a probability) and a policy action.
|
|
216
|
+
|
|
217
|
+
```ts
|
|
218
|
+
import { assess } from 'ribaunt';
|
|
219
|
+
|
|
220
|
+
const assessment = await assess({
|
|
221
|
+
signals: {
|
|
222
|
+
accountAgeSeconds: account.ageSeconds,
|
|
223
|
+
requestVelocity: requestsPerMinute,
|
|
224
|
+
userAgent: req.headers['user-agent'],
|
|
225
|
+
ip: req.ip, // accepted for custom scorers; default scorer ignores it
|
|
226
|
+
},
|
|
227
|
+
// thresholds: { challenge: 40, block: 80 }, // defaults shown
|
|
228
|
+
// scorer: myScorer, // optional pluggable RiskScorer
|
|
229
|
+
// workload: { minDifficulty: 3, maxDifficulty: 6, targetDurationMs: 750, calibration },
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
if (assessment.action === 'allow') { /* continue */ }
|
|
233
|
+
else if (assessment.action === 'challenge') {
|
|
234
|
+
const workload = assessment.workload!; // { difficulty, amount, estimatedAttempts } from selectWorkload()
|
|
235
|
+
}
|
|
236
|
+
else { /* block — application decides how to reject */ }
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
- `risk < challenge` → `allow` (no workload)
|
|
240
|
+
- `challenge ≤ risk < block` → `challenge` (with workload via existing `selectWorkload()`)
|
|
241
|
+
- `risk ≥ block` → `block` (no workload)
|
|
242
|
+
|
|
243
|
+
Defaults are `DEFAULT_RISK_THRESHOLDS = { challenge: 40, block: 80 }` — tune to your app; they are policy defaults, not fraud probabilities. Custom scorers (`RiskScorer { score(signals): Promise<number> }`) are `async` so you can call a remote model; invalid outputs (`NaN`/`Infinity`/`<0`/`>100`/non-number) are rejected, not clamped, and thrown errors propagate. See `docs/risk-engine.md` for the full default-scorer buckets, trust model, and limitations.
|
|
244
|
+
|
|
210
245
|
### `verifySolution(tokens, solutions, options?)`
|
|
211
246
|
|
|
212
247
|
Verifies submitted solutions and returns a structured result:
|
|
@@ -245,13 +280,15 @@ const rateLimiter = {
|
|
|
245
280
|
check: async (key?: string) => (await redisBucket.hit(key ?? 'unknown')) < 10,
|
|
246
281
|
};
|
|
247
282
|
|
|
248
|
-
// Challenge endpoint: key on the client IP
|
|
283
|
+
// Challenge endpoint: key the limiter on the client IP by closing over the
|
|
284
|
+
// request instead of passing `context`. Passing `context: req.ip` here would
|
|
285
|
+
// also cryptographically bind each challenge to that IP, forcing the verify
|
|
286
|
+
// call to pass the exact same `context` (see `verifySolution` below).
|
|
249
287
|
app.get('/api/captcha/challenge', async (req, res) => {
|
|
250
288
|
try {
|
|
251
289
|
const challenges = await createChallenge({
|
|
252
290
|
difficulty: 5,
|
|
253
291
|
amount: 4,
|
|
254
|
-
context: req.ip,
|
|
255
292
|
rateLimiter,
|
|
256
293
|
});
|
|
257
294
|
res.json({ challenges });
|
|
@@ -264,6 +301,8 @@ app.get('/api/captcha/challenge', async (req, res) => {
|
|
|
264
301
|
});
|
|
265
302
|
```
|
|
266
303
|
|
|
304
|
+
> **Context binding:** if you *do* pass `context` to `createChallenge()`, every challenge token is bound to that context via an HMAC digest. You must then pass the identical `context` to `verifySolution()` or verification fails with `context-mismatch`. Use `context` for binding a challenge to a specific action (e.g. one signup attempt), not for rate limiting.
|
|
305
|
+
|
|
267
306
|
### `onEvent` telemetry hook
|
|
268
307
|
|
|
269
308
|
Both entry points accept an optional synchronous `onEvent` callback for structured lifecycle events. It is fire-and-forget: a throwing consumer can never break challenge issuance or verification.
|
|
@@ -281,7 +320,7 @@ await createChallenge({ difficulty: 5, amount: 4, onEvent });
|
|
|
281
320
|
const result = await verifySolution(tokens, solutions, { onEvent });
|
|
282
321
|
```
|
|
283
322
|
|
|
284
|
-
`verify-failure` events carry the same `reason` values as `VerifySolutionResult` (`invalid-token`, `expired-token`, `invalid-solution`, `context-mismatch`, `replay-detected`, `configuration-error`). Add aggregation over Windows, StatsD, or Prometheus yourself — Ribaunt only emits raw events.
|
|
323
|
+
`verify-failure` events carry the same `reason` values as `VerifySolutionResult` (`invalid-token`, `expired-token`, `invalid-solution`, `context-mismatch`, `replay-detected`, `replay-store-unavailable`, `configuration-error`). Add aggregation over Windows, StatsD, or Prometheus yourself — Ribaunt only emits raw events.
|
|
285
324
|
|
|
286
325
|
### `solveChallenge(token, options?)`
|
|
287
326
|
|
|
@@ -306,6 +345,8 @@ const solutions = solveChallenge(challenges, {
|
|
|
306
345
|
show-warning="false"
|
|
307
346
|
warning-message="Verification may take longer on this device."
|
|
308
347
|
solve-timeout="15000"
|
|
348
|
+
worker-mode="preferred"
|
|
349
|
+
wasm-mode="preferred"
|
|
309
350
|
disabled="false"
|
|
310
351
|
></ribaunt-widget>
|
|
311
352
|
```
|
|
@@ -320,7 +361,9 @@ const solutions = solveChallenge(challenges, {
|
|
|
320
361
|
| `show-warning` | `showWarning` | Shows a warning banner. |
|
|
321
362
|
| `warning-message` | `warningMessage` | Custom warning text. |
|
|
322
363
|
| `show-progress` | `showProgress` | Set to `"false"` to swap the percentage ring for a plain bars spinner with a static `Loading...` label. |
|
|
323
|
-
| `solve-timeout` | `solveTimeout` | Optional
|
|
364
|
+
| `solve-timeout` | `solveTimeout` | Optional timeout in milliseconds for the whole verification attempt (fetching, solving, and verifying). |
|
|
365
|
+
| `worker-mode` | `workerMode` | Web Worker solving: `preferred` (default; falls back to the main thread), `required` (fail if unavailable), or `disabled`. |
|
|
366
|
+
| `wasm-mode` | `wasmMode` | WASM solver: `preferred` (default; uses WASM batch solver inside worker if available), `disabled` (always use JS solver inside worker). Independent from `worker-mode`. |
|
|
324
367
|
| `disabled` | `disabled` | Blocks user interaction and automatic verification. |
|
|
325
368
|
| | `fallback` | React-only. Custom loading element while widget dynamic import loads. Defaults to a built-in shimmer skeleton. |
|
|
326
369
|
|
|
@@ -353,6 +396,7 @@ See [theming docs](https://github.com/ribaunt/ribaunt/blob/main/docs/theming.md)
|
|
|
353
396
|
|
|
354
397
|
- [Quick start](https://github.com/ribaunt/ribaunt/blob/main/docs/quick-start.md)
|
|
355
398
|
- [Configuration](https://github.com/ribaunt/ribaunt/blob/main/docs/configuration.md)
|
|
399
|
+
- [Risk Engine](https://github.com/ribaunt/ribaunt/blob/main/docs/risk-engine.md)
|
|
356
400
|
- [Events](https://github.com/ribaunt/ribaunt/blob/main/docs/events.md)
|
|
357
401
|
- [HTML integration](https://github.com/ribaunt/ribaunt/blob/main/docs/integrations/html.md)
|
|
358
402
|
- [React integration](https://github.com/ribaunt/ribaunt/blob/main/docs/integrations/react.md)
|
|
@@ -371,9 +415,10 @@ You can also ask documentation-aware tools to use the Context7 library ID:
|
|
|
371
415
|
## Development
|
|
372
416
|
|
|
373
417
|
```bash
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
418
|
+
corepack enable
|
|
419
|
+
pnpm install
|
|
420
|
+
pnpm test
|
|
421
|
+
pnpm run build
|
|
377
422
|
```
|
|
378
423
|
|
|
379
424
|
## License
|
|
Binary file
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/// <reference lib="webworker" />
|
|
2
|
+
import { solveChallenge, decodeChallengeToken } from './solver.js';
|
|
3
|
+
import { ensureWasm, solveBatch, resetWasmHeap } from './wasm-solver.js';
|
|
4
|
+
const workerScope = self;
|
|
5
|
+
const activeControllers = new Map();
|
|
6
|
+
// Cache WASM initialization per worker lifetime (wasm-solver already caches, but keep explicit)
|
|
7
|
+
let wasmBackendState = 'uninitialized';
|
|
8
|
+
const WASM_BATCH_SIZE = 1024;
|
|
9
|
+
const yieldToEventLoop = (() => {
|
|
10
|
+
if (typeof MessageChannel !== 'function') {
|
|
11
|
+
return () => new Promise((resolve) => setTimeout(resolve, 0));
|
|
12
|
+
}
|
|
13
|
+
let channel = null;
|
|
14
|
+
const waiting = new Set();
|
|
15
|
+
return () => {
|
|
16
|
+
if (!channel) {
|
|
17
|
+
channel = new MessageChannel();
|
|
18
|
+
channel.port1.onmessage = () => {
|
|
19
|
+
for (const resolve of waiting)
|
|
20
|
+
resolve();
|
|
21
|
+
waiting.clear();
|
|
22
|
+
};
|
|
23
|
+
channel.port1.unref?.();
|
|
24
|
+
}
|
|
25
|
+
return new Promise((resolve) => {
|
|
26
|
+
waiting.add(resolve);
|
|
27
|
+
channel.port2.postMessage(null);
|
|
28
|
+
});
|
|
29
|
+
};
|
|
30
|
+
})();
|
|
31
|
+
async function solveSingleChallengeWasm(token, signal) {
|
|
32
|
+
const payload = decodeChallengeToken(token);
|
|
33
|
+
if (!payload)
|
|
34
|
+
return undefined;
|
|
35
|
+
const { challenge, difficulty } = payload;
|
|
36
|
+
let startNonce = 0;
|
|
37
|
+
try {
|
|
38
|
+
while (true) {
|
|
39
|
+
if (signal?.aborted) {
|
|
40
|
+
throw new DOMException('Challenge solving aborted', 'AbortError');
|
|
41
|
+
}
|
|
42
|
+
const result = solveBatch(challenge, startNonce, WASM_BATCH_SIZE, difficulty);
|
|
43
|
+
if (result.found && result.nonce && result.hash) {
|
|
44
|
+
// Validate before returning (wasm-solver already validated, but double-check)
|
|
45
|
+
if (typeof result.nonce !== 'string' || typeof result.hash !== 'string') {
|
|
46
|
+
throw new Error('Invalid WASM result shape');
|
|
47
|
+
}
|
|
48
|
+
return { nonce: result.nonce, hash: result.hash };
|
|
49
|
+
}
|
|
50
|
+
startNonce += WASM_BATCH_SIZE;
|
|
51
|
+
// Overflow guard - do not wrap; allow final batch where last nonce is 0x7fffffff
|
|
52
|
+
if (startNonce > 0x7fffffff - WASM_BATCH_SIZE + 1) {
|
|
53
|
+
throw new Error('WASM solver nonce overflow');
|
|
54
|
+
}
|
|
55
|
+
// Yield every 2048 nonces to keep worker responsive (matches JS solver's 2048)
|
|
56
|
+
if (startNonce % 2048 === 0) {
|
|
57
|
+
await yieldToEventLoop();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
finally {
|
|
62
|
+
// Reset heap to avoid leak between tokens/requests
|
|
63
|
+
try {
|
|
64
|
+
resetWasmHeap();
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
// ignore
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
async function solveChallengeWasm(tokens, onProgress, signal) {
|
|
72
|
+
const solutions = [];
|
|
73
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
74
|
+
const token = tokens[i];
|
|
75
|
+
if (!token)
|
|
76
|
+
throw new Error(`Invalid token at index ${i}`);
|
|
77
|
+
if (signal?.aborted)
|
|
78
|
+
throw new DOMException('Challenge solving aborted', 'AbortError');
|
|
79
|
+
const solution = await solveSingleChallengeWasm(token, signal);
|
|
80
|
+
if (!solution)
|
|
81
|
+
throw new Error(`Failed to solve challenge ${i + 1}`);
|
|
82
|
+
solutions.push(solution);
|
|
83
|
+
if (onProgress) {
|
|
84
|
+
const progress = Math.round(((i + 1) / tokens.length) * 100);
|
|
85
|
+
onProgress(progress);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return solutions;
|
|
89
|
+
}
|
|
90
|
+
async function selectBackend(wasmMode) {
|
|
91
|
+
if (wasmMode === 'disabled')
|
|
92
|
+
return 'js';
|
|
93
|
+
// preferred (default)
|
|
94
|
+
if (wasmBackendState === 'wasm-unavailable')
|
|
95
|
+
return 'js';
|
|
96
|
+
if (wasmBackendState === 'wasm-ready')
|
|
97
|
+
return 'wasm';
|
|
98
|
+
// uninitialized -> try to init
|
|
99
|
+
try {
|
|
100
|
+
const ok = await ensureWasm();
|
|
101
|
+
wasmBackendState = ok ? 'wasm-ready' : 'wasm-unavailable';
|
|
102
|
+
return ok ? 'wasm' : 'js';
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
wasmBackendState = 'wasm-unavailable';
|
|
106
|
+
return 'js';
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
workerScope.addEventListener('message', (event) => {
|
|
110
|
+
const request = event.data;
|
|
111
|
+
if (!request)
|
|
112
|
+
return;
|
|
113
|
+
if (request.type === 'cancel') {
|
|
114
|
+
const controller = activeControllers.get(request.id);
|
|
115
|
+
if (controller) {
|
|
116
|
+
controller.abort();
|
|
117
|
+
activeControllers.delete(request.id);
|
|
118
|
+
workerScope.postMessage({ type: 'cancelled', id: request.id });
|
|
119
|
+
workerScope.close();
|
|
120
|
+
}
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
if (request.type !== 'solve')
|
|
124
|
+
return;
|
|
125
|
+
const controller = new AbortController();
|
|
126
|
+
activeControllers.set(request.id, controller);
|
|
127
|
+
const wasmMode = request.wasmMode === 'disabled' ? 'disabled' : 'preferred';
|
|
128
|
+
(async () => {
|
|
129
|
+
const backend = await selectBackend(wasmMode);
|
|
130
|
+
// Telemetry: report backend selection once per request
|
|
131
|
+
if (!controller.signal.aborted) {
|
|
132
|
+
try {
|
|
133
|
+
workerScope.postMessage({ type: 'backend', id: request.id, backend });
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
// telemetry must never break solving
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
const solver = backend === 'wasm' ? solveChallengeWasm : solveChallenge;
|
|
140
|
+
// For wasm-unavailable fallback, re-select if wasm fails during solve? We already selected js
|
|
141
|
+
// But if backend is wasm and solve fails with internal error, we surface not fallback
|
|
142
|
+
return solver(request.tokens, (progress) => {
|
|
143
|
+
if (!controller.signal.aborted) {
|
|
144
|
+
workerScope.postMessage({
|
|
145
|
+
type: 'progress',
|
|
146
|
+
id: request.id,
|
|
147
|
+
progress,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}, controller.signal);
|
|
151
|
+
})().then((solutions) => {
|
|
152
|
+
activeControllers.delete(request.id);
|
|
153
|
+
if (controller.signal.aborted)
|
|
154
|
+
return;
|
|
155
|
+
workerScope.postMessage({
|
|
156
|
+
type: 'result',
|
|
157
|
+
id: request.id,
|
|
158
|
+
solutions,
|
|
159
|
+
});
|
|
160
|
+
}, (error) => {
|
|
161
|
+
activeControllers.delete(request.id);
|
|
162
|
+
if (controller.signal.aborted)
|
|
163
|
+
return;
|
|
164
|
+
// If WASM was selected and failed due to unexpected runtime error, surface as error
|
|
165
|
+
// For expected initialization failures we already fell back to JS, so this error is genuine
|
|
166
|
+
workerScope.postMessage({
|
|
167
|
+
type: 'error',
|
|
168
|
+
id: request.id,
|
|
169
|
+
error: error instanceof Error ? error.message : String(error),
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
export {};
|
|
174
|
+
//# sourceMappingURL=solver-worker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"solver-worker.js","sourceRoot":"","sources":["../src/solver-worker.ts"],"names":[],"mappings":"AAAA,iCAAiC;AAEjC,OAAO,EAAE,cAAc,EAAE,oBAAoB,EAA0B,MAAM,aAAa,CAAC;AAC3F,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAczE,MAAM,WAAW,GAAG,IAA6C,CAAC;AAClE,MAAM,iBAAiB,GAAG,IAAI,GAAG,EAA2B,CAAC;AAE7D,gGAAgG;AAChG,IAAI,gBAAgB,GAAwD,eAAe,CAAC;AAE5F,MAAM,eAAe,GAAG,IAAI,CAAC;AAE7B,MAAM,gBAAgB,GAAwB,CAAC,GAAG,EAAE;IAClD,IAAI,OAAO,cAAc,KAAK,UAAU,EAAE,CAAC;QACzC,OAAO,GAAG,EAAE,CAAC,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC;IACtE,CAAC;IACD,IAAI,OAAO,GAA0B,IAAI,CAAC;IAC1C,MAAM,OAAO,GAAG,IAAI,GAAG,EAAc,CAAC;IACtC,OAAO,GAAG,EAAE;QACV,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,OAAO,GAAG,IAAI,cAAc,EAAE,CAAC;YAC/B,OAAO,CAAC,KAAK,CAAC,SAAS,GAAG,GAAG,EAAE;gBAC7B,KAAK,MAAM,OAAO,IAAI,OAAO;oBAAE,OAAO,EAAE,CAAC;gBACzC,OAAO,CAAC,KAAK,EAAE,CAAC;YAClB,CAAC,CAAC;YACD,OAAO,CAAC,KAA2C,CAAC,KAAK,EAAE,EAAE,CAAC;QACjE,CAAC;QACD,OAAO,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;YACnC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YACrB,OAAQ,CAAC,KAAK,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;QACnC,CAAC,CAAC,CAAC;IACL,CAAC,CAAC;AACJ,CAAC,CAAC,EAAE,CAAC;AAEL,KAAK,UAAU,wBAAwB,CACrC,KAAa,EACb,MAAoB;IAEpB,MAAM,OAAO,GAAG,oBAAoB,CAAC,KAAK,CAAC,CAAC;IAC5C,IAAI,CAAC,OAAO;QAAE,OAAO,SAAS,CAAC;IAE/B,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC;IAC1C,IAAI,UAAU,GAAG,CAAC,CAAC;IAEnB,IAAI,CAAC;QACH,OAAO,IAAI,EAAE,CAAC;YACZ,IAAI,MAAM,EAAE,OAAO,EAAE,CAAC;gBACpB,MAAM,IAAI,YAAY,CAAC,2BAA2B,EAAE,YAAY,CAAC,CAAC;YACpE,CAAC;YAED,MAAM,MAAM,GAAG,UAAU,CAAC,SAAS,EAAE,UAAU,EAAE,eAAe,EAAE,UAAU,CAAC,CAAC;YAE9E,IAAI,MAAM,CAAC,KAAK,IAAI,MAAM,CAAC,KAAK,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;gBAChD,8EAA8E;gBAC9E,IAAI,OAAO,MAAM,CAAC,KAAK,KAAK,QAAQ,IAAI,OAAO,MAAM,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;oBACxE,MAAM,IAAI,KAAK,CAAC,2BAA2B,CAAC,CAAC;gBAC/C,CAAC;gBACD,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC;YACpD,CAAC;YAED,UAAU,IAAI,eAAe,CAAC;YAE9B,iFAAiF;YACjF,IAAI,UAAU,GAAG,UAAU,GAAG,eAAe,GAAG,CAAC,EAAE,CAAC;gBAClD,MAAM,IAAI,KAAK,CAAC,4BAA4B,CAAC,CAAC;YAChD,CAAC;YAED,+EAA+E;YAC/E,IAAI,UAAU,GAAG,IAAI,KAAK,CAAC,EAAE,CAAC;gBAC5B,MAAM,gBAAgB,EAAE,CAAC;YAC3B,CAAC;QACH,CAAC;IACH,CAAC;YAAS,CAAC;QACT,mDAAmD;QACnD,IAAI,CAAC;YACH,aAAa,EAAE,CAAC;QAClB,CAAC;QAAC,MAAM,CAAC;YACP,SAAS;QACX,CAAC;IACH,CAAC;AACH,CAAC;AAED,KAAK,UAAU,kBAAkB,CAC/B,MAAgB,EAChB,UAAuC,EACvC,MAAoB;IAEpB,MAAM,SAAS,GAAwB,EAAE,CAAC;IAC1C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACvC,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;QACxB,IAAI,CAAC,KAAK;YAAE,MAAM,IAAI,KAAK,CAAC,0BAA0B,CAAC,EAAE,CAAC,CAAC;QAC3D,IAAI,MAAM,EAAE,OAAO;YAAE,MAAM,IAAI,YAAY,CAAC,2BAA2B,EAAE,YAAY,CAAC,CAAC;QACvF,MAAM,QAAQ,GAAG,MAAM,wBAAwB,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;QAC/D,IAAI,CAAC,QAAQ;YAAE,MAAM,IAAI,KAAK,CAAC,6BAA6B,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACrE,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACzB,IAAI,UAAU,EAAE,CAAC;YACf,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,GAAG,GAAG,CAAC,CAAC;YAC7D,UAAU,CAAC,QAAQ,CAAC,CAAC;QACvB,CAAC;IACH,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,KAAK,UAAU,aAAa,CAAC,QAA8B;IACzD,IAAI,QAAQ,KAAK,UAAU;QAAE,OAAO,IAAI,CAAC;IACzC,sBAAsB;IACtB,IAAI,gBAAgB,KAAK,kBAAkB;QAAE,OAAO,IAAI,CAAC;IACzD,IAAI,gBAAgB,KAAK,YAAY;QAAE,OAAO,MAAM,CAAC;IAErD,+BAA+B;IAC/B,IAAI,CAAC;QACH,MAAM,EAAE,GAAG,MAAM,UAAU,EAAE,CAAC;QAC9B,gBAAgB,GAAG,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,kBAAkB,CAAC;QAC1D,OAAO,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;IAC5B,CAAC;IAAC,MAAM,CAAC;QACP,gBAAgB,GAAG,kBAAkB,CAAC;QACtC,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,WAAW,CAAC,gBAAgB,CAAC,SAAS,EAAE,CAAC,KAAkC,EAAE,EAAE;IAC7E,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC;IAC3B,IAAI,CAAC,OAAO;QAAE,OAAO;IAErB,IAAI,OAAO,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC9B,MAAM,UAAU,GAAG,iBAAiB,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QACrD,IAAI,UAAU,EAAE,CAAC;YACf,UAAU,CAAC,KAAK,EAAE,CAAC;YACnB,iBAAiB,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;YACrC,WAAW,CAAC,WAAW,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE,EAA2B,CAAC,CAAC;YACxF,WAAW,CAAC,KAAK,EAAE,CAAC;QACtB,CAAC;QACD,OAAO;IACT,CAAC;IAED,IAAI,OAAO,CAAC,IAAI,KAAK,OAAO;QAAE,OAAO;IAErC,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;IACzC,iBAAiB,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,EAAE,UAAU,CAAC,CAAC;IAE9C,MAAM,QAAQ,GAAa,OAAO,CAAC,QAAQ,KAAK,UAAU,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,WAAW,CAAC;IAEtF,CAAC,KAAK,IAAI,EAAE;QACV,MAAM,OAAO,GAAG,MAAM,aAAa,CAAC,QAAQ,CAAC,CAAC;QAE9C,uDAAuD;QACvD,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YAC/B,IAAI,CAAC;gBACH,WAAW,CAAC,WAAW,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE,EAAE,OAAO,EAA2B,CAAC,CAAC;YACjG,CAAC;YAAC,MAAM,CAAC;gBACP,qCAAqC;YACvC,CAAC;QACH,CAAC;QAED,MAAM,MAAM,GAAG,OAAO,KAAK,MAAM,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,cAAc,CAAC;QAExE,8FAA8F;QAC9F,sFAAsF;QAEtF,OAAO,MAAM,CACX,OAAO,CAAC,MAAM,EACd,CAAC,QAAQ,EAAE,EAAE;YACX,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;gBAC/B,WAAW,CAAC,WAAW,CAAC;oBACtB,IAAI,EAAE,UAAU;oBAChB,EAAE,EAAE,OAAO,CAAC,EAAE;oBACd,QAAQ;iBACgB,CAAC,CAAC;YAC9B,CAAC;QACH,CAAC,EACD,UAAU,CAAC,MAAM,CAClB,CAAC;IACJ,CAAC,CAAC,EAAE,CAAC,IAAI,CACP,CAAC,SAAS,EAAE,EAAE;QACZ,iBAAiB,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QACrC,IAAI,UAAU,CAAC,MAAM,CAAC,OAAO;YAAE,OAAO;QACtC,WAAW,CAAC,WAAW,CAAC;YACtB,IAAI,EAAE,QAAQ;YACd,EAAE,EAAE,OAAO,CAAC,EAAE;YACd,SAAS;SACe,CAAC,CAAC;IAC9B,CAAC,EACD,CAAC,KAAc,EAAE,EAAE;QACjB,iBAAiB,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QACrC,IAAI,UAAU,CAAC,MAAM,CAAC,OAAO;YAAE,OAAO;QACtC,oFAAoF;QACpF,4FAA4F;QAC5F,WAAW,CAAC,WAAW,CAAC;YACtB,IAAI,EAAE,OAAO;YACb,EAAE,EAAE,OAAO,CAAC,EAAE;YACd,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;SACrC,CAAC,CAAC;IAC9B,CAAC,CACF,CAAC;AACJ,CAAC,CAAC,CAAC;AAEH,OAAO,EAAE,CAAC"}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser-compatible challenge solver using Web Crypto API
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Decode JWT token (browser-compatible, without verification)
|
|
6
|
+
*/
|
|
7
|
+
function decodeJWT(token) {
|
|
8
|
+
try {
|
|
9
|
+
const parts = token.split('.');
|
|
10
|
+
if (parts.length !== 3 || !parts[1])
|
|
11
|
+
return null;
|
|
12
|
+
const normalizedPayload = parts[1].replace(/-/g, '+').replace(/_/g, '/');
|
|
13
|
+
const paddedPayload = normalizedPayload.padEnd(normalizedPayload.length + ((4 - (normalizedPayload.length % 4)) % 4), '=');
|
|
14
|
+
const payload = JSON.parse(atob(paddedPayload));
|
|
15
|
+
return payload;
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* SHA-256 hash using Web Crypto API
|
|
23
|
+
*/
|
|
24
|
+
async function sha256(message) {
|
|
25
|
+
if (typeof TextEncoder === 'undefined') {
|
|
26
|
+
throw new Error('TextEncoder is unavailable in this browser environment');
|
|
27
|
+
}
|
|
28
|
+
if (!globalThis.crypto?.subtle) {
|
|
29
|
+
throw new Error('Web Crypto API is unavailable. Use HTTPS or localhost.');
|
|
30
|
+
}
|
|
31
|
+
const msgBuffer = new TextEncoder().encode(message);
|
|
32
|
+
const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer);
|
|
33
|
+
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
34
|
+
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
|
|
35
|
+
return hashHex;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Yield to the event loop without the ~4ms clamping browsers apply to
|
|
39
|
+
* nested setTimeout calls. MessageChannel tasks run as macrotasks with
|
|
40
|
+
* microsecond-level latency, keeping the UI responsive at a fraction of
|
|
41
|
+
* the timer overhead.
|
|
42
|
+
*
|
|
43
|
+
* The channel is created lazily on first yield, and the receiving port is
|
|
44
|
+
* unref'd where supported (Node.js): a port with an active message listener
|
|
45
|
+
* otherwise keeps the host event loop alive and prevents clean exit.
|
|
46
|
+
*/
|
|
47
|
+
const yieldToEventLoop = (() => {
|
|
48
|
+
if (typeof MessageChannel !== 'function') {
|
|
49
|
+
return () => new Promise((resolve) => setTimeout(resolve, 0));
|
|
50
|
+
}
|
|
51
|
+
let channel = null;
|
|
52
|
+
const waiting = new Set();
|
|
53
|
+
return () => {
|
|
54
|
+
if (!channel) {
|
|
55
|
+
channel = new MessageChannel();
|
|
56
|
+
channel.port1.onmessage = () => {
|
|
57
|
+
for (const resolve of waiting)
|
|
58
|
+
resolve();
|
|
59
|
+
waiting.clear();
|
|
60
|
+
};
|
|
61
|
+
channel.port1.unref?.();
|
|
62
|
+
}
|
|
63
|
+
return new Promise((resolve) => {
|
|
64
|
+
waiting.add(resolve);
|
|
65
|
+
channel.port2.postMessage(null);
|
|
66
|
+
});
|
|
67
|
+
};
|
|
68
|
+
})();
|
|
69
|
+
export async function calibrateBrowser(iterations = 128) {
|
|
70
|
+
if (!Number.isFinite(iterations) || iterations < 1) {
|
|
71
|
+
throw new Error('Calibration iterations must be at least 1');
|
|
72
|
+
}
|
|
73
|
+
const normalizedIterations = Math.floor(iterations);
|
|
74
|
+
const startedAt = performance.now();
|
|
75
|
+
for (let index = 0; index < normalizedIterations; index++) {
|
|
76
|
+
await sha256(`ribaunt-calibration:${index}`);
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
iterations: normalizedIterations,
|
|
80
|
+
durationMs: Math.max(1, Math.round(performance.now() - startedAt)),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
export const calibrateClient = calibrateBrowser;
|
|
84
|
+
export function decodeChallengeToken(token) {
|
|
85
|
+
return decodeJWT(token);
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Solve a single challenge token (browser-compatible)
|
|
89
|
+
*/
|
|
90
|
+
export async function solveSingleChallenge(token, signal) {
|
|
91
|
+
const payload = decodeJWT(token);
|
|
92
|
+
if (!payload)
|
|
93
|
+
return undefined;
|
|
94
|
+
const { challenge, difficulty } = payload;
|
|
95
|
+
const prefix = '0'.repeat(difficulty);
|
|
96
|
+
let nonce = 0;
|
|
97
|
+
while (true) {
|
|
98
|
+
if (signal?.aborted) {
|
|
99
|
+
throw new DOMException('Challenge solving aborted', 'AbortError');
|
|
100
|
+
}
|
|
101
|
+
const hash = await sha256(`${challenge}${nonce}`);
|
|
102
|
+
if (hash.startsWith(prefix)) {
|
|
103
|
+
return { nonce: String(nonce), hash };
|
|
104
|
+
}
|
|
105
|
+
nonce++;
|
|
106
|
+
// Yield to keep the UI responsive; each batch amortizes the yield cost.
|
|
107
|
+
if (nonce % 2048 === 0) {
|
|
108
|
+
await yieldToEventLoop();
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Solve multiple challenge tokens (browser-compatible)
|
|
114
|
+
*/
|
|
115
|
+
export async function solveChallenge(tokens, onProgress, signal) {
|
|
116
|
+
const solutions = [];
|
|
117
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
118
|
+
const token = tokens[i];
|
|
119
|
+
if (!token) {
|
|
120
|
+
throw new Error(`Invalid token at index ${i}`);
|
|
121
|
+
}
|
|
122
|
+
const solution = await solveSingleChallenge(token, signal);
|
|
123
|
+
if (!solution) {
|
|
124
|
+
throw new Error(`Failed to solve challenge ${i + 1}`);
|
|
125
|
+
}
|
|
126
|
+
solutions.push(solution);
|
|
127
|
+
// Report progress
|
|
128
|
+
if (onProgress) {
|
|
129
|
+
const progress = Math.round(((i + 1) / tokens.length) * 100);
|
|
130
|
+
onProgress(progress);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return solutions;
|
|
134
|
+
}
|
|
135
|
+
//# sourceMappingURL=solver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"solver.js","sourceRoot":"","sources":["../src/solver.ts"],"names":[],"mappings":"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"}
|