ribaunt 0.2.4 → 0.2.5
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/package.json +4 -1
- package/SECURITY.md +0 -36
- package/demo/lib/ribaunt-solver.wasm +0 -0
- package/demo/lib/solver-worker.js +0 -174
- package/demo/lib/solver-worker.js.map +0 -1
- package/demo/lib/solver.js +0 -135
- package/demo/lib/solver.js.map +0 -1
- package/demo/lib/wasm-solver.js +0 -296
- package/demo/lib/wasm-solver.js.map +0 -1
- package/demo/lib/widget-browser.js +0 -9
- package/demo/lib/widget-browser.js.map +0 -1
- package/demo/lib/widget.js +0 -942
- package/demo/lib/widget.js.map +0 -1
- package/demo/lib/worker-client.js +0 -128
- package/demo/lib/worker-client.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ribaunt",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.5",
|
|
4
4
|
"description": "A PoW CAPTCHA library that provides stateless and secure user verification",
|
|
5
|
+
"files": [
|
|
6
|
+
"dist"
|
|
7
|
+
],
|
|
5
8
|
"homepage": "https://ribaunt.com",
|
|
6
9
|
"bugs": {
|
|
7
10
|
"url": "https://github.com/ribaunt/ribaunt/issues"
|
package/SECURITY.md
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
# Security Policy
|
|
2
|
-
|
|
3
|
-
Ribaunt is a proof-of-work CAPTCHA library, so security reports are especially
|
|
4
|
-
important when they affect challenge signing, verification bypasses, replay
|
|
5
|
-
prevention, widget behavior, or server-only secret handling.
|
|
6
|
-
|
|
7
|
-
## Supported Versions
|
|
8
|
-
|
|
9
|
-
| Version | Security support |
|
|
10
|
-
| --- | --- |
|
|
11
|
-
| Latest npm release | Supported |
|
|
12
|
-
| Older releases | Best effort |
|
|
13
|
-
|
|
14
|
-
## Reporting a Vulnerability
|
|
15
|
-
|
|
16
|
-
Please do not open a public GitHub issue for suspected vulnerabilities.
|
|
17
|
-
|
|
18
|
-
Report vulnerabilities privately through GitHub Security Advisories:
|
|
19
|
-
|
|
20
|
-
https://github.com/ribaunt/ribaunt/security/advisories/new
|
|
21
|
-
|
|
22
|
-
Include as much of the following as you can:
|
|
23
|
-
|
|
24
|
-
- Affected Ribaunt version or commit
|
|
25
|
-
- Reproduction steps
|
|
26
|
-
- Expected and observed behavior
|
|
27
|
-
- Security impact
|
|
28
|
-
- Runtime and browser environment, if relevant
|
|
29
|
-
- Proof of concept, logs, or screenshots
|
|
30
|
-
|
|
31
|
-
## What to Expect
|
|
32
|
-
|
|
33
|
-
After receiving a report, maintainers will acknowledge it, investigate the
|
|
34
|
-
issue, and coordinate a fix when appropriate. If the issue is confirmed, the fix
|
|
35
|
-
will be released and a security advisory will be published when disclosure is
|
|
36
|
-
safe.
|
|
Binary file
|
|
@@ -1,174 +0,0 @@
|
|
|
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
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"solver-worker.js","sourceRoot":"","sources":["../src/solver-worker.ts"],"names":[],"mappings":"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"}
|
package/demo/lib/solver.js
DELETED
|
@@ -1,135 +0,0 @@
|
|
|
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
|
package/demo/lib/solver.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"solver.js","sourceRoot":"","sources":["../src/solver.ts"],"names":[],"mappings":"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"}
|
package/demo/lib/wasm-solver.js
DELETED
|
@@ -1,296 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* TypeScript adapter for the WASM SHA-256 solver.
|
|
3
|
-
* Contains all WASM-specific implementation details and validates boundary values.
|
|
4
|
-
*/
|
|
5
|
-
let wasmInstance = null;
|
|
6
|
-
let wasmState = 'uninitialized';
|
|
7
|
-
let loadPromise = null;
|
|
8
|
-
let cachedChallenge = null;
|
|
9
|
-
let cachedPtr = 0;
|
|
10
|
-
let cachedLen = 0;
|
|
11
|
-
const VALID_HASH_RE = /^[a-f0-9]{64}$/;
|
|
12
|
-
const VALID_NONCE_RE = /^\d+$/;
|
|
13
|
-
const VALID_SHA256_HEX_RE = /^[a-f0-9]{64}$/i;
|
|
14
|
-
// Embedded SHA-256 of dist/ribaunt-solver.wasm for integrity verification.
|
|
15
|
-
// Recompute with `shasum -a 256 dist/ribaunt-solver.wasm` after rebuilding wasm.
|
|
16
|
-
const EMBEDDED_WASM_SHA256 = '774398452596d67491a6ee5bd6291c9665dc4fc1a83db15f14dbbb4058f74c3e';
|
|
17
|
-
function isValidHash(hash) {
|
|
18
|
-
return VALID_HASH_RE.test(hash);
|
|
19
|
-
}
|
|
20
|
-
function isValidNonce(nonce) {
|
|
21
|
-
return nonce.length > 0 && VALID_NONCE_RE.test(nonce);
|
|
22
|
-
}
|
|
23
|
-
function bytesToHex(bytes) {
|
|
24
|
-
return Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');
|
|
25
|
-
}
|
|
26
|
-
async function sha256Hex(bytes) {
|
|
27
|
-
const subtle = typeof crypto !== 'undefined' ? crypto.subtle : undefined;
|
|
28
|
-
if (subtle) {
|
|
29
|
-
const digest = await subtle.digest('SHA-256', bytes);
|
|
30
|
-
return bytesToHex(new Uint8Array(digest));
|
|
31
|
-
}
|
|
32
|
-
// Node fallback when Web Crypto is unavailable
|
|
33
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
34
|
-
const nodeCrypto = await import('node:crypto');
|
|
35
|
-
return nodeCrypto.createHash('sha256').update(bytes).digest('hex');
|
|
36
|
-
}
|
|
37
|
-
function getExpectedWasmSha256() {
|
|
38
|
-
const g = globalThis;
|
|
39
|
-
const fromGlobal = typeof g.__RIBAUNT_WASM_SHA256__ === 'string' ? g.__RIBAUNT_WASM_SHA256__.trim().toLowerCase() : '';
|
|
40
|
-
if (fromGlobal && VALID_SHA256_HEX_RE.test(fromGlobal))
|
|
41
|
-
return fromGlobal;
|
|
42
|
-
const proc = typeof process !== 'undefined' ? process : undefined;
|
|
43
|
-
const fromEnv = proc?.env?.RIBAUNT_WASM_SHA256?.trim().toLowerCase() ?? '';
|
|
44
|
-
if (fromEnv && VALID_SHA256_HEX_RE.test(fromEnv))
|
|
45
|
-
return fromEnv;
|
|
46
|
-
if (EMBEDDED_WASM_SHA256 && VALID_SHA256_HEX_RE.test(EMBEDDED_WASM_SHA256))
|
|
47
|
-
return EMBEDDED_WASM_SHA256.toLowerCase();
|
|
48
|
-
return null;
|
|
49
|
-
}
|
|
50
|
-
async function loadWasmBytes() {
|
|
51
|
-
const candidates = [];
|
|
52
|
-
try {
|
|
53
|
-
candidates.push(new URL('./ribaunt-solver.wasm', import.meta.url));
|
|
54
|
-
}
|
|
55
|
-
catch (_e) {
|
|
56
|
-
void _e;
|
|
57
|
-
}
|
|
58
|
-
try {
|
|
59
|
-
candidates.push(new URL('../dist/ribaunt-solver.wasm', import.meta.url));
|
|
60
|
-
}
|
|
61
|
-
catch (_e) {
|
|
62
|
-
void _e;
|
|
63
|
-
}
|
|
64
|
-
try {
|
|
65
|
-
candidates.push(new URL('./dist/ribaunt-solver.wasm', import.meta.url));
|
|
66
|
-
}
|
|
67
|
-
catch (_e) {
|
|
68
|
-
void _e;
|
|
69
|
-
}
|
|
70
|
-
// Browser / worker path: try fetch for each candidate
|
|
71
|
-
if (typeof fetch === 'function') {
|
|
72
|
-
for (const cand of candidates) {
|
|
73
|
-
try {
|
|
74
|
-
const res = await fetch(cand);
|
|
75
|
-
if (res.ok) {
|
|
76
|
-
const buf = await res.arrayBuffer();
|
|
77
|
-
if (buf.byteLength > 0)
|
|
78
|
-
return new Uint8Array(buf);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
catch (_e) {
|
|
82
|
-
void _e;
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
// Node.js fallback: try fs for candidates plus cwd
|
|
87
|
-
const fsCandidates = [...candidates];
|
|
88
|
-
try {
|
|
89
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
90
|
-
const pathMod = await import('node:path');
|
|
91
|
-
const cwd = typeof process !== 'undefined' ? process.cwd() : '';
|
|
92
|
-
if (cwd) {
|
|
93
|
-
fsCandidates.push(pathMod.resolve(cwd, 'dist/ribaunt-solver.wasm'));
|
|
94
|
-
fsCandidates.push(pathMod.resolve(cwd, 'dist/cjs/ribaunt-solver.wasm'));
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
catch (_e) {
|
|
98
|
-
void _e;
|
|
99
|
-
}
|
|
100
|
-
try {
|
|
101
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
102
|
-
const fs = await import('node:fs/promises');
|
|
103
|
-
for (const cand of fsCandidates) {
|
|
104
|
-
try {
|
|
105
|
-
const bytes = await fs.readFile(cand);
|
|
106
|
-
if (bytes && bytes.length > 0)
|
|
107
|
-
return bytes;
|
|
108
|
-
}
|
|
109
|
-
catch (_e) {
|
|
110
|
-
void _e;
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
catch (_e) {
|
|
115
|
-
void _e;
|
|
116
|
-
}
|
|
117
|
-
throw new Error('WASM asset fetch failure');
|
|
118
|
-
}
|
|
119
|
-
async function instantiateWasm() {
|
|
120
|
-
if (typeof WebAssembly === 'undefined')
|
|
121
|
-
return false;
|
|
122
|
-
try {
|
|
123
|
-
const bytes = await loadWasmBytes();
|
|
124
|
-
const expectedSha256 = getExpectedWasmSha256();
|
|
125
|
-
if (!expectedSha256)
|
|
126
|
-
return false;
|
|
127
|
-
const actualSha256 = await sha256Hex(bytes);
|
|
128
|
-
if (actualSha256 !== expectedSha256)
|
|
129
|
-
return false;
|
|
130
|
-
const mod = await WebAssembly.compile(bytes);
|
|
131
|
-
const instance = await WebAssembly.instantiate(mod, {});
|
|
132
|
-
const exp = instance.exports;
|
|
133
|
-
if (!exp.memory || typeof exp.solve_batch !== 'function' || typeof exp.get_hash_ptr !== 'function' || typeof exp.alloc !== 'function') {
|
|
134
|
-
return false;
|
|
135
|
-
}
|
|
136
|
-
wasmInstance = instance;
|
|
137
|
-
wasmState = 'wasm-ready';
|
|
138
|
-
return true;
|
|
139
|
-
}
|
|
140
|
-
catch {
|
|
141
|
-
return false;
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
export async function ensureWasm() {
|
|
145
|
-
if (wasmState === 'wasm-ready')
|
|
146
|
-
return true;
|
|
147
|
-
if (wasmState === 'wasm-unavailable')
|
|
148
|
-
return false;
|
|
149
|
-
if (loadPromise)
|
|
150
|
-
return loadPromise;
|
|
151
|
-
loadPromise = (async () => {
|
|
152
|
-
const ok = await instantiateWasm();
|
|
153
|
-
wasmState = ok ? 'wasm-ready' : 'wasm-unavailable';
|
|
154
|
-
return ok;
|
|
155
|
-
})();
|
|
156
|
-
return loadPromise;
|
|
157
|
-
}
|
|
158
|
-
export function getWasmState() {
|
|
159
|
-
return wasmState;
|
|
160
|
-
}
|
|
161
|
-
export function resetWasmForTesting() {
|
|
162
|
-
wasmInstance = null;
|
|
163
|
-
wasmState = 'uninitialized';
|
|
164
|
-
loadPromise = null;
|
|
165
|
-
cachedChallenge = null;
|
|
166
|
-
cachedPtr = 0;
|
|
167
|
-
cachedLen = 0;
|
|
168
|
-
}
|
|
169
|
-
// For testing: allow injecting failure or mock
|
|
170
|
-
export function setWasmUnavailableForTesting() {
|
|
171
|
-
wasmState = 'wasm-unavailable';
|
|
172
|
-
wasmInstance = null;
|
|
173
|
-
loadPromise = Promise.resolve(false);
|
|
174
|
-
}
|
|
175
|
-
export function isWasmAvailable() {
|
|
176
|
-
return wasmState === 'wasm-ready';
|
|
177
|
-
}
|
|
178
|
-
export function resetWasmHeap() {
|
|
179
|
-
cachedChallenge = null;
|
|
180
|
-
cachedPtr = 0;
|
|
181
|
-
cachedLen = 0;
|
|
182
|
-
if (wasmInstance) {
|
|
183
|
-
try {
|
|
184
|
-
const exp = wasmInstance.exports;
|
|
185
|
-
exp.reset_heap?.();
|
|
186
|
-
}
|
|
187
|
-
catch {
|
|
188
|
-
// ignore
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
/**
|
|
193
|
-
* Synchronous batch solver - must be called after ensureWasm() succeeds.
|
|
194
|
-
* Encodes challenge as UTF-8, allocates in WASM memory, calls solve_batch,
|
|
195
|
-
* validates and returns result.
|
|
196
|
-
*/
|
|
197
|
-
export function solveBatch(challenge, startNonce, batchSize, difficulty) {
|
|
198
|
-
if (wasmState !== 'wasm-ready' || !wasmInstance) {
|
|
199
|
-
throw new Error('WASM solver not initialized');
|
|
200
|
-
}
|
|
201
|
-
// Validate inputs
|
|
202
|
-
if (!Number.isInteger(difficulty) || difficulty < 1 || difficulty > 64) {
|
|
203
|
-
throw new Error('Invalid difficulty');
|
|
204
|
-
}
|
|
205
|
-
if (!Number.isInteger(batchSize) || batchSize <= 0 || batchSize > 16384) {
|
|
206
|
-
throw new Error('Invalid batchSize');
|
|
207
|
-
}
|
|
208
|
-
if (!Number.isInteger(startNonce) || startNonce < 0 || !Number.isFinite(startNonce)) {
|
|
209
|
-
throw new Error('Invalid startNonce');
|
|
210
|
-
}
|
|
211
|
-
if (typeof challenge !== 'string') {
|
|
212
|
-
throw new Error('Invalid challenge');
|
|
213
|
-
}
|
|
214
|
-
// Overflow guard before calling WASM (also checked inside)
|
|
215
|
-
if (startNonce > 0xffffffff - batchSize + 1) {
|
|
216
|
-
throw new Error('Nonce range exceeds u32');
|
|
217
|
-
}
|
|
218
|
-
// Also guard signed limit to avoid sentinel confusion (max 2^31-1 for v1); allow final batch where last nonce is 0x7fffffff
|
|
219
|
-
if (startNonce > 0x7fffffff || startNonce + batchSize > 0x7fffffff + 1) {
|
|
220
|
-
throw new Error('Nonce exceeds wasm signed limit');
|
|
221
|
-
}
|
|
222
|
-
const exp = wasmInstance.exports;
|
|
223
|
-
const mem = exp.memory;
|
|
224
|
-
let ptr;
|
|
225
|
-
let challengeLen;
|
|
226
|
-
if (cachedChallenge === challenge && cachedPtr !== 0) {
|
|
227
|
-
ptr = cachedPtr;
|
|
228
|
-
challengeLen = cachedLen;
|
|
229
|
-
}
|
|
230
|
-
else {
|
|
231
|
-
// Encode challenge as UTF-8 bytes
|
|
232
|
-
const encoder = new TextEncoder();
|
|
233
|
-
const challengeBytes = encoder.encode(challenge);
|
|
234
|
-
if (challengeBytes.length > 1014) {
|
|
235
|
-
throw new Error('Challenge too long for WASM solver');
|
|
236
|
-
}
|
|
237
|
-
const newPtr = exp.alloc(challengeBytes.length);
|
|
238
|
-
// Refresh view after possible growth
|
|
239
|
-
const memU8 = new Uint8Array(mem.buffer);
|
|
240
|
-
// Bounds check: ensure ptr + len within memory
|
|
241
|
-
if (newPtr < 0 || newPtr + challengeBytes.length > memU8.length) {
|
|
242
|
-
throw new Error('WASM memory allocation out of bounds');
|
|
243
|
-
}
|
|
244
|
-
memU8.set(challengeBytes, newPtr);
|
|
245
|
-
cachedChallenge = challenge;
|
|
246
|
-
cachedPtr = newPtr;
|
|
247
|
-
cachedLen = challengeBytes.length;
|
|
248
|
-
ptr = newPtr;
|
|
249
|
-
challengeLen = cachedLen;
|
|
250
|
-
}
|
|
251
|
-
let result;
|
|
252
|
-
try {
|
|
253
|
-
result = exp.solve_batch(ptr, challengeLen, startNonce >>> 0, batchSize, difficulty);
|
|
254
|
-
}
|
|
255
|
-
catch (e) {
|
|
256
|
-
throw new Error(`WASM solver trap: ${e instanceof Error ? e.message : String(e)}`, { cause: e });
|
|
257
|
-
}
|
|
258
|
-
if (result === -1) {
|
|
259
|
-
return { found: false };
|
|
260
|
-
}
|
|
261
|
-
if (result === -2) {
|
|
262
|
-
throw new Error('WASM solver overflow');
|
|
263
|
-
}
|
|
264
|
-
if (result < 0) {
|
|
265
|
-
throw new Error(`WASM solver internal error: ${result}`);
|
|
266
|
-
}
|
|
267
|
-
// Validate nonce
|
|
268
|
-
const nonceStr = String(result >>> 0);
|
|
269
|
-
if (!isValidNonce(nonceStr)) {
|
|
270
|
-
throw new Error('WASM returned invalid nonce');
|
|
271
|
-
}
|
|
272
|
-
// Read hash
|
|
273
|
-
const hashPtr = exp.get_hash_ptr();
|
|
274
|
-
const hashLen = 32;
|
|
275
|
-
const mem2 = new Uint8Array(mem.buffer);
|
|
276
|
-
if (hashPtr < 0 || hashPtr + hashLen > mem2.length) {
|
|
277
|
-
throw new Error('WASM hash pointer out of bounds');
|
|
278
|
-
}
|
|
279
|
-
const hashBytes = mem2.slice(hashPtr, hashPtr + hashLen);
|
|
280
|
-
const hashHex = Array.from(hashBytes).map(b => b.toString(16).padStart(2, '0')).join('');
|
|
281
|
-
if (!isValidHash(hashHex)) {
|
|
282
|
-
throw new Error('WASM returned invalid hash');
|
|
283
|
-
}
|
|
284
|
-
if (!hashHex.startsWith('0'.repeat(difficulty))) {
|
|
285
|
-
throw new Error('WASM returned hash that does not satisfy difficulty');
|
|
286
|
-
}
|
|
287
|
-
// Additional validation: ensure hash corresponds to challenge+nonce (defense in depth)
|
|
288
|
-
// We trust WASM but validate shape; deeper verification (re-hashing in JS) could be done
|
|
289
|
-
// but would duplicate work. We just validate invariants.
|
|
290
|
-
return { found: true, nonce: nonceStr, hash: hashHex };
|
|
291
|
-
}
|
|
292
|
-
// Re-export for worker to use TextEncoder check
|
|
293
|
-
export function isTextEncoderAvailable() {
|
|
294
|
-
return typeof TextEncoder !== 'undefined';
|
|
295
|
-
}
|
|
296
|
-
//# sourceMappingURL=wasm-solver.js.map
|