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 CHANGED
@@ -1,7 +1,10 @@
1
1
  {
2
2
  "name": "ribaunt",
3
- "version": "0.2.4",
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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"}
@@ -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
@@ -1 +0,0 @@
1
- {"version":3,"file":"solver.js","sourceRoot":"","sources":["../src/solver.ts"],"names":[],"mappings":"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"}
@@ -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