fix(deepseek-web): replace unlicensed PoW artifacts (#11732)

Merged via /merge-batch (v3.8.51 provenance sweep). Boarded and validated together with the batch's other provenance/dependency PRs in a combined worktree — full gate suite green. Thank you.
This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-08-28 04:44:34 -03:00
committed by GitHub
parent 700a735949
commit 657d3a484a
12 changed files with 916 additions and 213 deletions

View File

@@ -0,0 +1 @@
- **chore(deepseek-web):** remove the provenance-unresolved DeepSeek PoW WASM binary and its runtime loader/tracing while retaining the existing JavaScript solver (slower at high difficulty) ([#11732](https://github.com/diegosouzapw/OmniRoute/pull/11732)).

View File

@@ -317,11 +317,6 @@
"count": 1
}
},
"open-sse/lib/deepseek-pow.ts": {
"@typescript-eslint/no-explicit-any": {
"count": 5
}
},
"open-sse/mcp-server/__tests__/a2aLifecycle.test.ts": {
"@typescript-eslint/no-explicit-any": {
"count": 1

View File

@@ -244,8 +244,8 @@ const nextConfig = {
"./src/mitm/server.cjs",
"./open-sse/services/compression/engines/rtk/filters/**/*.json",
"./open-sse/services/compression/rules/**/*.json",
"./open-sse/lib/sha3_wasm_bg.wasm",
"./open-sse/lib/deepseek-pow-solver.cjs",
"./open-sse/lib/deepseek-pow-hash.js",
"./open-sse/lib/deepseek-pow-worker.mjs",
// sql.js WASM is loaded at runtime by the sqljsAdapter fallback tier
// (better-sqlite3 → node:sqlite → sql.js). Next traces sql-wasm.js but can
// omit the runtime sql-wasm.wasm asset from the standalone bundle.

View File

@@ -141,13 +141,14 @@ function generateFakeCookie(): string {
// ── PoW Solver (DeepSeekHashV1) ─────────────────────────────────────────
async function solvePow(challenge: PowChallenge): Promise<string> {
async function solvePow(challenge: PowChallenge, signal?: AbortSignal | null): Promise<string> {
const answer = await solveDeepSeekPowAsync(
challenge.algorithm,
challenge.challenge,
challenge.salt,
challenge.difficulty,
challenge.expire_at
challenge.expire_at,
{ signal }
);
if (answer < 0) throw new Error("PoW solver failed");
return Buffer.from(
@@ -566,11 +567,7 @@ export function messagesToPrompt(
}
const effectiveWindow =
historyWindow > 0
? historyWindow
: conversation.length > 1
? DEFAULT_AUTO_HISTORY_WINDOW
: 0;
historyWindow > 0 ? historyWindow : conversation.length > 1 ? DEFAULT_AUTO_HISTORY_WINDOW : 0;
if (effectiveWindow > 0 && conversation.length > 1) {
// Rolling-window transcript of the most recent turns (#2942, auto-applied per
@@ -932,7 +929,7 @@ export class DeepSeekWebExecutor extends BaseExecutor {
// One completion attempt against a given session id (fresh PoW per attempt).
const performCompletion = async (sid: string) => {
const powChallenge = await getPowChallenge(accessToken, signal);
const powAnswer = await solvePow(powChallenge);
const powAnswer = await solvePow(powChallenge, signal);
const reqHeaders: Record<string, string> = {
...FAKE_HEADERS,
"Content-Type": "application/json",
@@ -1145,7 +1142,7 @@ export class DeepSeekWebExecutor extends BaseExecutor {
const msg = err instanceof Error ? err.message : String(err);
log?.error?.("DEEPSEEK-WEB", `Execute failed: ${msg}`);
if (err instanceof DOMException && err.name === "AbortError") {
if (err instanceof Error && err.name === "AbortError") {
return {
response: errorResponse(499, "Request cancelled"),
url: COMPLETION_URL,

View File

@@ -0,0 +1,450 @@
// Clean-room reference derived from NIST FIPS 202, Algorithms 1-9. FIPS SHA3-256
// uses KECCAK-p[1600,24]. Differential testing against the supplied black-box
// vectors identifies DeepSeekHashV1 as the same sponge construction with
// KECCAK-p[1600,23] (the last 23 rounds, with round indices 1 through 23).
const LANE_MASK = (1n << 64n) - 1n;
const SHA3_256_RATE_BYTES = 136;
const SHA3_DOMAIN_SUFFIX = 0x06;
const SHA3_256_OUTPUT_BYTES = 32;
// Indexed as x + 5*y, matching the FIPS 202 state coordinates.
const ROTATION_OFFSETS = [
0, 1, 62, 28, 27, 36, 44, 6, 55, 20, 3, 10, 43, 25, 39, 41, 45, 15, 21, 8, 18, 2, 61, 56, 14,
];
const ROUND_CONSTANTS = [
0x0000000000000001n,
0x0000000000008082n,
0x800000000000808an,
0x8000000080008000n,
0x000000000000808bn,
0x0000000080000001n,
0x8000000080008081n,
0x8000000000008009n,
0x000000000000008an,
0x0000000000000088n,
0x0000000080008009n,
0x000000008000000an,
0x000000008000808bn,
0x800000000000008bn,
0x8000000000008089n,
0x8000000000008003n,
0x8000000000008002n,
0x8000000000000080n,
0x000000000000800an,
0x800000008000000an,
0x8000000080008081n,
0x8000000000008080n,
0x0000000080000001n,
0x8000000080008008n,
];
/**
* @param {bigint} value
* @param {number} amount
* @returns {bigint}
*/
function rotateLeft64(value, amount) {
if (amount === 0) return value;
const shift = BigInt(amount);
return ((value << shift) | (value >> (64n - shift))) & LANE_MASK;
}
/**
* Apply the last `roundCount` rounds of KECCAK-p[1600, roundCount].
*
* @param {bigint[]} state
* @param {number} roundCount
*/
function keccakP1600Reference(state, roundCount) {
/** @type {bigint[]} */
const columnParity = new Array(5).fill(0n);
/** @type {bigint[]} */
const thetaMix = new Array(5).fill(0n);
/** @type {bigint[]} */
const rhoPiState = new Array(25).fill(0n);
const firstRound = ROUND_CONSTANTS.length - roundCount;
for (let round = firstRound; round < ROUND_CONSTANTS.length; round++) {
for (let x = 0; x < 5; x++) {
columnParity[x] = state[x] ^ state[x + 5] ^ state[x + 10] ^ state[x + 15] ^ state[x + 20];
}
for (let x = 0; x < 5; x++) {
thetaMix[x] = columnParity[(x + 4) % 5] ^ rotateLeft64(columnParity[(x + 1) % 5], 1);
}
for (let y = 0; y < 5; y++) {
for (let x = 0; x < 5; x++) state[x + 5 * y] ^= thetaMix[x];
}
for (let y = 0; y < 5; y++) {
for (let x = 0; x < 5; x++) {
const destinationX = y;
const destinationY = (2 * x + 3 * y) % 5;
const lane = x + 5 * y;
rhoPiState[destinationX + 5 * destinationY] = rotateLeft64(
state[lane],
ROTATION_OFFSETS[lane]
);
}
}
for (let y = 0; y < 5; y++) {
const row = 5 * y;
for (let x = 0; x < 5; x++) {
state[x + row] =
rhoPiState[x + row] ^
(~rhoPiState[((x + 1) % 5) + row] & LANE_MASK & rhoPiState[((x + 2) % 5) + row]);
}
}
state[0] ^= ROUND_CONSTANTS[round];
}
}
/**
* @param {bigint[]} state
* @param {Uint8Array} block
* @param {number} roundCount
*/
function absorbReferenceBlock(state, block, roundCount) {
for (let index = 0; index < SHA3_256_RATE_BYTES; index++) {
const lane = Math.floor(index / 8);
const shift = BigInt((index % 8) * 8);
state[lane] ^= BigInt(block[index]) << shift;
}
keccakP1600Reference(state, roundCount);
}
/**
* SHA3-256's sponge parameters with a selectable KECCAK-p round count.
*
* @param {string} input
* @param {number} roundCount
* @returns {string}
*/
function sha3_256ReferenceWithRoundCount(input, roundCount) {
const bytes = new TextEncoder().encode(input);
/** @type {bigint[]} */
const state = new Array(25).fill(0n);
let offset = 0;
while (offset + SHA3_256_RATE_BYTES <= bytes.length) {
absorbReferenceBlock(state, bytes.subarray(offset, offset + SHA3_256_RATE_BYTES), roundCount);
offset += SHA3_256_RATE_BYTES;
}
const finalBlock = new Uint8Array(SHA3_256_RATE_BYTES);
finalBlock.set(bytes.subarray(offset));
finalBlock[bytes.length - offset] ^= SHA3_DOMAIN_SUFFIX;
finalBlock[SHA3_256_RATE_BYTES - 1] ^= 0x80;
absorbReferenceBlock(state, finalBlock, roundCount);
const output = new Uint8Array(SHA3_256_OUTPUT_BYTES);
for (let index = 0; index < output.length; index++) {
const lane = Math.floor(index / 8);
const shift = BigInt((index % 8) * 8);
output[index] = Number((state[lane] >> shift) & 0xffn);
}
return Array.from(output, (byte) => byte.toString(16).padStart(2, "0")).join("");
}
/**
* Readable FIPS 202 control implementation used to validate the permutation,
* byte order, padding, and multi-block absorption against `node:crypto`.
*
* @param {string} input
* @returns {string}
*/
export function sha3_256Fips202Reference(input) {
return sha3_256ReferenceWithRoundCount(input, 24);
}
/**
* Readable DeepSeekHashV1 reference model. Runtime searches use an equivalent
* 32-bit implementation below so a bounded synchronous compatibility call does
* not turn a 144k challenge into minutes of BigInt work.
*
* @param {string} input
* @returns {string}
*/
export function deepSeekHashV1Reference(input) {
return sha3_256ReferenceWithRoundCount(input, 23);
}
const DEEPSEEK_HASH_ROUNDS = 23;
const DIGEST_HEX_PATTERN = /^[a-f0-9]{64}$/i;
const ROUND_CONSTANTS_LOW = Uint32Array.from(ROUND_CONSTANTS, (value) =>
Number(value & 0xffffffffn)
);
const ROUND_CONSTANTS_HIGH = Uint32Array.from(ROUND_CONSTANTS, (value) =>
Number((value >> 32n) & 0xffffffffn)
);
const RHO_PI_DESTINATION_WORDS = Uint8Array.from({ length: 25 }, (_, lane) => {
const x = lane % 5;
const y = Math.floor(lane / 5);
return 2 * (y + 5 * ((2 * x + 3 * y) % 5));
});
const CHI_NEXT_WORDS = Uint8Array.from({ length: 25 }, (_, lane) => {
const x = lane % 5;
const row = lane - x;
return 2 * (row + ((x + 1) % 5));
});
const CHI_NEXT_NEXT_WORDS = Uint8Array.from({ length: 25 }, (_, lane) => {
const x = lane % 5;
const row = lane - x;
return 2 * (row + ((x + 2) % 5));
});
const HEX_DIGITS = "0123456789abcdef";
export const MAX_DEEPSEEK_POW_DIFFICULTY = 250_000;
/**
* Equivalent 32-bit form of the reference permutation. Each 64-bit lane is
* stored as adjacent little-endian low/high uint32 words.
*
* @param {Uint32Array} state
* @param {Uint32Array} rhoPiState
* @param {Uint32Array} columnParity
* @param {Uint32Array} thetaMix
* @param {number} roundCount
*/
function keccakP1600Uint32(state, rhoPiState, columnParity, thetaMix, roundCount) {
const firstRound = ROUND_CONSTANTS.length - roundCount;
for (let round = firstRound; round < ROUND_CONSTANTS.length; round++) {
for (let x = 0; x < 5; x++) {
const word = 2 * x;
columnParity[word] =
state[word] ^ state[word + 10] ^ state[word + 20] ^ state[word + 30] ^ state[word + 40];
columnParity[word + 1] =
state[word + 1] ^ state[word + 11] ^ state[word + 21] ^ state[word + 31] ^ state[word + 41];
}
for (let x = 0; x < 5; x++) {
const previous = 2 * ((x + 4) % 5);
const next = 2 * ((x + 1) % 5);
const rotatedLow = (columnParity[next] << 1) | (columnParity[next + 1] >>> 31);
const rotatedHigh = (columnParity[next + 1] << 1) | (columnParity[next] >>> 31);
thetaMix[2 * x] = columnParity[previous] ^ rotatedLow;
thetaMix[2 * x + 1] = columnParity[previous + 1] ^ rotatedHigh;
}
for (let x = 0; x < 5; x++) {
const word = 2 * x;
const low = thetaMix[word];
const high = thetaMix[word + 1];
state[word] ^= low;
state[word + 1] ^= high;
state[word + 10] ^= low;
state[word + 11] ^= high;
state[word + 20] ^= low;
state[word + 21] ^= high;
state[word + 30] ^= low;
state[word + 31] ^= high;
state[word + 40] ^= low;
state[word + 41] ^= high;
}
rhoPiState[0] = state[0];
rhoPiState[1] = state[1];
for (let lane = 1; lane < 25; lane++) {
const source = 2 * lane;
const destination = RHO_PI_DESTINATION_WORDS[lane];
const amount = ROTATION_OFFSETS[lane];
const low = state[source];
const high = state[source + 1];
if (amount < 32) {
rhoPiState[destination] = (low << amount) | (high >>> (32 - amount));
rhoPiState[destination + 1] = (high << amount) | (low >>> (32 - amount));
} else {
const reduced = amount - 32;
rhoPiState[destination] = (high << reduced) | (low >>> (32 - reduced));
rhoPiState[destination + 1] = (low << reduced) | (high >>> (32 - reduced));
}
}
for (let lane = 0; lane < 25; lane++) {
const word = 2 * lane;
const nextWord = CHI_NEXT_WORDS[lane];
const nextNextWord = CHI_NEXT_NEXT_WORDS[lane];
state[word] = rhoPiState[word] ^ (~rhoPiState[nextWord] & rhoPiState[nextNextWord]);
state[word + 1] =
rhoPiState[word + 1] ^ (~rhoPiState[nextWord + 1] & rhoPiState[nextNextWord + 1]);
}
state[0] ^= ROUND_CONSTANTS_LOW[round];
state[1] ^= ROUND_CONSTANTS_HIGH[round];
}
}
/**
* @param {Uint32Array} state
* @param {Uint8Array} bytes
* @param {number} offset
* @param {Uint32Array} rhoPiState
* @param {Uint32Array} columnParity
* @param {Uint32Array} thetaMix
* @param {number} roundCount
*/
function absorbFullUint32Block(
state,
bytes,
offset,
rhoPiState,
columnParity,
thetaMix,
roundCount
) {
for (let index = 0; index < SHA3_256_RATE_BYTES; index++) {
const word = index >>> 2;
state[word] ^= bytes[offset + index] << ((index & 3) * 8);
}
keccakP1600Uint32(state, rhoPiState, columnParity, thetaMix, roundCount);
}
/**
* @param {Uint32Array} state
* @returns {string}
*/
function digestStateToHex(state) {
let digest = "";
for (let index = 0; index < SHA3_256_OUTPUT_BYTES; index++) {
const byte = (state[index >>> 2] >>> ((index & 3) * 8)) & 0xff;
digest += HEX_DIGITS[byte >>> 4] + HEX_DIGITS[byte & 0x0f];
}
return digest;
}
/**
* @param {string} input
* @param {number} roundCount
* @returns {string}
*/
function sha3_256Uint32WithRoundCount(input, roundCount) {
const bytes = new TextEncoder().encode(input);
const state = new Uint32Array(50);
const rhoPiState = new Uint32Array(50);
const columnParity = new Uint32Array(10);
const thetaMix = new Uint32Array(10);
let offset = 0;
while (offset + SHA3_256_RATE_BYTES <= bytes.length) {
absorbFullUint32Block(state, bytes, offset, rhoPiState, columnParity, thetaMix, roundCount);
offset += SHA3_256_RATE_BYTES;
}
const remaining = bytes.length - offset;
for (let index = 0; index < remaining; index++) {
state[index >>> 2] ^= bytes[offset + index] << ((index & 3) * 8);
}
state[remaining >>> 2] ^= SHA3_DOMAIN_SUFFIX << ((remaining & 3) * 8);
state[(SHA3_256_RATE_BYTES - 1) >>> 2] ^= 0x80 << 24;
keccakP1600Uint32(state, rhoPiState, columnParity, thetaMix, roundCount);
return digestStateToHex(state);
}
/**
* Optimized, allocation-bounded DeepSeekHashV1 digest.
*
* @param {string} input
* @returns {string}
*/
export function deepSeekHashV1(input) {
return sha3_256Uint32WithRoundCount(input, DEEPSEEK_HASH_ROUNDS);
}
/**
* @param {string} digestHex
* @returns {Uint32Array}
*/
function parseDigestWords(digestHex) {
const words = new Uint32Array(SHA3_256_OUTPUT_BYTES / 4);
for (let index = 0; index < SHA3_256_OUTPUT_BYTES; index++) {
const byte = Number.parseInt(digestHex.slice(index * 2, index * 2 + 2), 16);
words[index >>> 2] |= byte << ((index & 3) * 8);
}
return words;
}
/**
* Search `prefix + nonce` without allocating a digest or re-encoding the prefix
* for every candidate. Inputs are validated here as well as at the public solver
* boundary so the worker cannot be coerced into an unbounded loop.
*
* @param {string} prefix
* @param {string} challenge
* @param {number} difficulty
* @returns {number}
*/
export function findDeepSeekPowNonce(prefix, challenge, difficulty) {
if (typeof prefix !== "string") throw new TypeError("DeepSeek PoW prefix must be a string");
if (!DIGEST_HEX_PATTERN.test(challenge)) {
throw new TypeError("DeepSeek PoW challenge must be a 64-character hex digest");
}
if (
!Number.isSafeInteger(difficulty) ||
difficulty < 1 ||
difficulty > MAX_DEEPSEEK_POW_DIFFICULTY
) {
throw new RangeError(
`DeepSeek PoW difficulty must be an integer from 1 to ${MAX_DEEPSEEK_POW_DIFFICULTY}`
);
}
const prefixBytes = new TextEncoder().encode(prefix);
const baseState = new Uint32Array(50);
const rhoPiState = new Uint32Array(50);
const columnParity = new Uint32Array(10);
const thetaMix = new Uint32Array(10);
let prefixOffset = 0;
while (prefixOffset + SHA3_256_RATE_BYTES <= prefixBytes.length) {
absorbFullUint32Block(
baseState,
prefixBytes,
prefixOffset,
rhoPiState,
columnParity,
thetaMix,
DEEPSEEK_HASH_ROUNDS
);
prefixOffset += SHA3_256_RATE_BYTES;
}
const tailLength = prefixBytes.length - prefixOffset;
const tailWords = new Uint32Array(Math.ceil(tailLength / 4));
for (let index = 0; index < tailLength; index++) {
tailWords[index >>> 2] ^= prefixBytes[prefixOffset + index] << ((index & 3) * 8);
}
const targetWords = parseDigestWords(challenge.toLowerCase());
const state = new Uint32Array(50);
nonceLoop: for (let nonce = 0; nonce < difficulty; nonce++) {
state.set(baseState);
for (let word = 0; word < tailWords.length; word++) state[word] ^= tailWords[word];
let position = tailLength;
const nonceText = String(nonce);
for (let index = 0; index < nonceText.length; index++) {
state[position >>> 2] ^= nonceText.charCodeAt(index) << ((position & 3) * 8);
position += 1;
if (position === SHA3_256_RATE_BYTES) {
keccakP1600Uint32(state, rhoPiState, columnParity, thetaMix, DEEPSEEK_HASH_ROUNDS);
position = 0;
}
}
state[position >>> 2] ^= SHA3_DOMAIN_SUFFIX << ((position & 3) * 8);
state[(SHA3_256_RATE_BYTES - 1) >>> 2] ^= 0x80 << 24;
keccakP1600Uint32(state, rhoPiState, columnParity, thetaMix, DEEPSEEK_HASH_ROUNDS);
for (let word = 0; word < targetWords.length; word++) {
if (state[word] !== targetWords[word]) continue nonceLoop;
}
return nonce;
}
return -1;
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,20 @@
import { parentPort, workerData } from "node:worker_threads";
import { findDeepSeekPowNonce, MAX_DEEPSEEK_POW_DIFFICULTY } from "./deepseek-pow-hash.js";
if (!parentPort) {
throw new Error("DeepSeek PoW worker requires a parent port");
}
const { challenge, prefix, difficulty } = workerData;
if (
typeof challenge !== "string" ||
typeof prefix !== "string" ||
!Number.isSafeInteger(difficulty) ||
difficulty < 1 ||
difficulty > MAX_DEEPSEEK_POW_DIFFICULTY
) {
throw new Error("DeepSeek PoW worker received invalid input");
}
parentPort.postMessage(findDeepSeekPowNonce(prefix, challenge, difficulty));

View File

@@ -1,181 +1,156 @@
import { createRequire } from "node:module";
import fs from "node:fs";
import { existsSync } from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { Worker } from "node:worker_threads";
// ── WASM solver (fast — ~50-100ms at difficulty 144000) ──────────────────
import { findDeepSeekPowNonce, MAX_DEEPSEEK_POW_DIFFICULTY } from "./deepseek-pow-hash.js";
class DeepSeekHashWasm {
private wasmInstance: any;
private offset = 0;
private cachedUint8Memory: Uint8Array | null = null;
private cachedTextEncoder = new TextEncoder();
const DEEPSEEK_POW_ALGORITHM = "DeepSeekHashV1";
const SHA3_256_HEX_PATTERN = /^[a-f0-9]{64}$/i;
const MAX_DIFFICULTY = MAX_DEEPSEEK_POW_DIFFICULTY;
const MAX_SALT_LENGTH = 1_024;
const MAX_CONCURRENT_WORKERS = 2;
const DEFAULT_TIMEOUT_MS = 10_000;
const MAX_TIMEOUT_MS = 30_000;
private getCachedUint8Memory(): Uint8Array {
if (!this.cachedUint8Memory?.byteLength) {
this.cachedUint8Memory = new Uint8Array(this.wasmInstance.memory.buffer);
}
return this.cachedUint8Memory;
}
let activeWorkerCount = 0;
private encodeString(
text: string,
allocate: (size: number, align: number) => number,
reallocate: (ptr: number, oldSize: number, newSize: number, align: number) => number
): number {
const strLength = text.length;
let ptr = allocate(strLength, 1) >>> 0;
const memory = this.getCachedUint8Memory();
let asciiLength = 0;
for (; asciiLength < strLength; asciiLength++) {
if (text.charCodeAt(asciiLength) > 127) break;
memory[ptr + asciiLength] = text.charCodeAt(asciiLength);
}
if (asciiLength !== strLength) {
if (asciiLength > 0) text = text.slice(asciiLength);
ptr = reallocate(ptr, strLength, asciiLength + text.length * 3, 1) >>> 0;
const result = this.cachedTextEncoder.encodeInto(
text,
this.getCachedUint8Memory().subarray(ptr + asciiLength, ptr + asciiLength + text.length * 3)
);
asciiLength += result.written!;
ptr = reallocate(ptr, asciiLength + text.length * 3, asciiLength, 1) >>> 0;
}
this.offset = asciiLength;
return ptr;
}
calculateHash(challenge: string, prefix: string, difficulty: number): number | undefined {
try {
const retptr = this.wasmInstance.__wbindgen_add_to_stack_pointer(-16);
const ptr0 = this.encodeString(
challenge,
this.wasmInstance.__wbindgen_export_0,
this.wasmInstance.__wbindgen_export_1
);
const len0 = this.offset;
const ptr1 = this.encodeString(
prefix,
this.wasmInstance.__wbindgen_export_0,
this.wasmInstance.__wbindgen_export_1
);
const len1 = this.offset;
this.wasmInstance.wasm_solve(retptr, ptr0, len0, ptr1, len1, difficulty);
const dv = new DataView(this.wasmInstance.memory.buffer);
const status = dv.getInt32(retptr + 0, true);
const value = dv.getFloat64(retptr + 8, true);
return status === 0 ? undefined : value;
} finally {
this.wasmInstance.__wbindgen_add_to_stack_pointer(16);
}
}
async init(wasmPath: string): Promise<void> {
const wasmBuffer = await fs.promises.readFile(wasmPath);
const { instance } = await WebAssembly.instantiate(wasmBuffer, { wbg: {} });
this.wasmInstance = instance.exports;
}
export interface SolveDeepSeekPowOptions {
signal?: AbortSignal | null;
timeoutMs?: number;
}
let _wasmSolver: DeepSeekHashWasm | null = null;
let _wasmInitFailed = false;
async function getWasmSolver(): Promise<DeepSeekHashWasm | null> {
if (_wasmInitFailed) return null;
if (_wasmSolver) return _wasmSolver;
try {
const solver = new DeepSeekHashWasm();
const wasmPath = path.join(path.dirname(fileURLToPath(import.meta.url)), "sha3_wasm_bg.wasm");
await solver.init(wasmPath);
_wasmSolver = solver;
return solver;
} catch {
_wasmInitFailed = true;
return null;
}
interface ValidatedChallenge {
challenge: string;
prefix: string;
difficulty: number;
}
// ── JS fallback solver (slow — ~5-6s at difficulty 144000) ───────────────
const require = createRequire(import.meta.url);
let _U: any | undefined;
function loadU(): any {
if (_U === undefined) {
_U = require("./deepseek-pow-solver.cjs").U;
}
return _U;
function createAbortError(): Error {
const error = new Error("DeepSeek PoW computation aborted");
error.name = "AbortError";
return error;
}
function solveWithJS(challenge: string, prefix: string, difficulty: number): number {
const U = loadU();
const createHash = () => {
const self: any = {};
self._sponge = new U({ capacity: 256, padding: 6 });
self.update = (s: string) => {
self._sponge.absorb(Buffer.from(s, "utf8"));
return self;
};
self.digest = (fmt?: string) => {
return self._sponge.squeeze(6).toString(fmt || "hex");
};
self.copy = () => {
const c: any = {};
c._sponge = self._sponge.copy();
c.update = (s: string) => {
c._sponge.absorb(Buffer.from(s, "utf8"));
return c;
};
c.digest = (fmt?: string) => {
return c._sponge.squeeze(6).toString(fmt || "hex");
};
return c;
};
return self;
function validateChallenge(
algorithm: string,
challenge: string,
salt: string,
difficulty: number,
expireAt: number
): ValidatedChallenge {
if (algorithm !== DEEPSEEK_POW_ALGORITHM) {
throw new Error(`Unsupported DeepSeek PoW algorithm: ${algorithm}`);
}
if (!SHA3_256_HEX_PATTERN.test(challenge)) {
throw new Error("DeepSeek PoW challenge must be a 64-character SHA3-256 hex digest");
}
if (typeof salt !== "string" || salt.length === 0 || salt.length > MAX_SALT_LENGTH) {
throw new Error(`DeepSeek PoW salt must contain 1-${MAX_SALT_LENGTH} characters`);
}
if (!Number.isSafeInteger(difficulty) || difficulty < 1 || difficulty > MAX_DIFFICULTY) {
throw new Error(`DeepSeek PoW difficulty must be an integer from 1 to ${MAX_DIFFICULTY}`);
}
if (!Number.isSafeInteger(expireAt) || expireAt < 0) {
throw new Error("DeepSeek PoW expiry must be a non-negative safe integer");
}
return {
challenge: challenge.toLowerCase(),
prefix: `${salt}_${expireAt}_`,
difficulty,
};
const h = createHash();
h.update(prefix);
for (let nonce = 0; nonce < difficulty; nonce++) {
if (h.copy().update(String(nonce)).digest("hex") === challenge) {
return nonce;
}
}
return -1;
}
// ── Public API ───────────────────────────────────────────────────────────
function resolveTimeoutMs(timeoutMs: number | undefined): number {
if (timeoutMs === undefined) return DEFAULT_TIMEOUT_MS;
if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > MAX_TIMEOUT_MS) {
throw new Error(`DeepSeek PoW timeout must be an integer from 1 to ${MAX_TIMEOUT_MS}ms`);
}
return timeoutMs;
}
function solveSynchronously({ challenge, prefix, difficulty }: ValidatedChallenge): number {
return findDeepSeekPowNonce(prefix, challenge, difficulty);
}
function resolveWorkerPath(): string {
const tracedPath = path.join(process.cwd(), "open-sse/lib/deepseek-pow-worker.mjs");
if (existsSync(tracedPath)) return tracedPath;
return fileURLToPath(new URL("./deepseek-pow-worker.mjs", import.meta.url));
}
function solveInWorker(
validated: ValidatedChallenge,
options: SolveDeepSeekPowOptions
): Promise<number> {
const { signal } = options;
if (signal?.aborted) return Promise.reject(createAbortError());
if (activeWorkerCount >= MAX_CONCURRENT_WORKERS) {
return Promise.reject(
new Error(`DeepSeek PoW worker capacity reached (${MAX_CONCURRENT_WORKERS})`)
);
}
const timeoutMs = resolveTimeoutMs(options.timeoutMs);
activeWorkerCount += 1;
return new Promise<number>((resolve, reject) => {
const worker = new Worker(resolveWorkerPath(), { workerData: validated });
let settled = false;
const cleanup = () => {
clearTimeout(timeout);
signal?.removeEventListener("abort", onAbort);
activeWorkerCount = Math.max(0, activeWorkerCount - 1);
};
const finish = (callback: () => void, terminate: boolean) => {
if (settled) return;
settled = true;
cleanup();
if (terminate) void worker.terminate();
callback();
};
const onAbort = () => {
finish(() => reject(createAbortError()), true);
};
const timeout = setTimeout(() => {
finish(() => reject(new Error(`DeepSeek PoW computation exceeded ${timeoutMs}ms`)), true);
}, timeoutMs);
signal?.addEventListener("abort", onAbort, { once: true });
worker.once("message", (answer: unknown) => {
if (!Number.isSafeInteger(answer) || (answer as number) < -1) {
finish(() => reject(new Error("DeepSeek PoW worker returned an invalid answer")), true);
return;
}
finish(() => resolve(answer as number), false);
});
worker.once("error", (error) => {
finish(() => reject(error), true);
});
worker.once("exit", (code) => {
if (code !== 0) {
finish(() => reject(new Error(`DeepSeek PoW worker exited with code ${code}`)), false);
} else {
finish(() => reject(new Error("DeepSeek PoW worker exited without an answer")), false);
}
});
});
}
export async function solveDeepSeekPowAsync(
algorithm: string,
challenge: string,
salt: string,
difficulty: number,
expireAt: number
expireAt: number,
options: SolveDeepSeekPowOptions = {}
): Promise<number> {
if (algorithm !== "DeepSeekHashV1") throw new Error(`Unsupported: ${algorithm}`);
const prefix = `${salt}_${expireAt}_`;
const wasm = await getWasmSolver();
if (wasm) {
const answer = wasm.calculateHash(challenge, prefix, difficulty);
if (answer === undefined) return -1;
return answer;
}
return solveWithJS(challenge, prefix, difficulty);
const validated = validateChallenge(algorithm, challenge, salt, difficulty, expireAt);
return solveInWorker(validated, options);
}
// Sync wrapper kept for backward compat (uses JS fallback only)
export function solveDeepSeekPow(
algorithm: string,
challenge: string,
@@ -183,7 +158,9 @@ export function solveDeepSeekPow(
difficulty: number,
expireAt: number
): number {
if (algorithm !== "DeepSeekHashV1") throw new Error(`Unsupported: ${algorithm}`);
const prefix = `${salt}_${expireAt}_`;
return solveWithJS(challenge, prefix, difficulty);
// Compatibility-only synchronous API. The validated 250k ceiling bounds CPU
// use; request handling must use solveDeepSeekPowAsync() so hashing stays off
// the event loop and remains abortable.
const validated = validateChallenge(algorithm, challenge, salt, difficulty, expireAt);
return solveSynchronously(validated);
}

Binary file not shown.

View File

@@ -0,0 +1,238 @@
import assert from "node:assert/strict";
import { createHash } from "node:crypto";
import { existsSync, readFileSync } from "node:fs";
import test from "node:test";
import { solveDeepSeekPow, solveDeepSeekPowAsync } from "../../open-sse/lib/deepseek-pow.ts";
import {
deepSeekHashV1,
deepSeekHashV1Reference,
sha3_256Fips202Reference,
} from "../../open-sse/lib/deepseek-pow-hash.js";
const repoRoot = new URL("../../", import.meta.url);
const noMatchChallenge = "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff";
const redistributedArtifacts = [
new URL("../../open-sse/lib/sha3_wasm_bg.wasm", import.meta.url),
new URL("../../open-sse/lib/deepseek-pow-solver.cjs", import.meta.url),
];
const deepSeekHashV1Vectors = [
[
"1122334455667788_1778891543095_0",
"311b26ae1e0fe7375e242958ce46db5552a6c67fea3f96880dcd846c63a74286",
],
[
"1122334455667788_1778891543095_1",
"526f9103dfe22bcda9481b7f304a157b8edb18c5bb96a2061eefec1fbd0db706",
],
["vector_42_0", "1813da791ddac0da3a225e5878ff3d3ea07577e048f8e9575b82a274b71e3810"],
["vector_42_1", "b582fdb4f83662baec734e23373efc860515c03c40decbef076697eb5832f83c"],
["vector_42_2", "48b348ad54c372f78ccb6a26cbf156668f4c6a5b7d5a1cf19ccd78888586019b"],
["vector_42_3", "2ffed26ea9e1d6f4bbe49a266d98fe04ad9a5ad4c6d765862de8d18c513c3815"],
["bb_1778891543095_0", "705e5d630f02d09a8179c6a0fcb0caf7265f08fb206fadca0301224f4422fc64"],
] as const;
test("DeepSeekHashV1 is SHA3-256 with the last 23 KECCAK-p[1600] rounds", () => {
for (const [input, expected] of deepSeekHashV1Vectors) {
assert.equal(deepSeekHashV1Reference(input), expected, input);
}
});
test("the 24-round FIPS 202 reference agrees with node:crypto on a separate corpus", () => {
const corpus = [
"",
"abc",
"café",
"🧪",
"a".repeat(135),
"b".repeat(136),
"c".repeat(137),
"multi-block-".repeat(40),
];
for (const input of corpus) {
const expected = createHash("sha3-256").update(input).digest("hex");
assert.equal(sha3_256Fips202Reference(input), expected, JSON.stringify(input));
}
});
test("the optimized DeepSeekHashV1 implementation agrees with the readable reference", () => {
const corpus = [
...deepSeekHashV1Vectors.map(([input]) => input),
"",
"café-🧪",
"a".repeat(135),
"b".repeat(136),
"c".repeat(137),
"multi-block-".repeat(40),
];
for (const input of corpus) {
assert.equal(deepSeekHashV1(input), deepSeekHashV1Reference(input), JSON.stringify(input));
}
});
test("DeepSeek PoW remains functional without redistributing extracted solver artifacts", async () => {
const answer = await solveDeepSeekPowAsync(
"DeepSeekHashV1",
"311b26ae1e0fe7375e242958ce46db5552a6c67fea3f96880dcd846c63a74286",
"1122334455667788",
1,
1778891543095
);
assert.equal(answer, 0, "the retained JavaScript solver must satisfy the known PoW vector");
for (const artifact of redistributedArtifacts) {
assert.equal(existsSync(artifact), false, `${artifact.pathname} must not be redistributed`);
}
assert.equal(
existsSync(new URL("../../open-sse/lib/deepseek-pow-hash.js", import.meta.url)),
true,
"the clean-room JavaScript hash core must be packaged"
);
assert.equal(
existsSync(new URL("../../open-sse/lib/deepseek-pow-worker.mjs", import.meta.url)),
true,
"the asynchronous worker entry must be packaged"
);
for (const relativePath of [
"open-sse/lib/deepseek-pow-hash.js",
"open-sse/lib/deepseek-pow.ts",
"open-sse/lib/deepseek-pow-worker.mjs",
"next.config.mjs",
"package.json",
"open-sse/package.json",
]) {
const contents = readFileSync(new URL(relativePath, repoRoot), "utf8");
assert.doesNotMatch(
contents,
/sha3_wasm_bg\.wasm|deepseek-pow-solver\.cjs/,
`${relativePath} must not reference an extracted solver artifact`
);
}
for (const relativePath of [
"open-sse/lib/deepseek-pow-hash.js",
"open-sse/lib/deepseek-pow.ts",
"open-sse/lib/deepseek-pow-worker.mjs",
]) {
const contents = readFileSync(new URL(relativePath, repoRoot), "utf8");
assert.doesNotMatch(
contents,
/createRequire|WebAssembly/,
`${relativePath} must not dynamically load a binary solver`
);
}
const nextConfig = readFileSync(new URL("../../next.config.mjs", import.meta.url), "utf8");
assert.match(nextConfig, /\.\/open-sse\/lib\/deepseek-pow-hash\.js/);
assert.match(nextConfig, /\.\/open-sse\/lib\/deepseek-pow-worker\.mjs/);
});
test("DeepSeek PoW rejects malformed or unsafe challenges before hashing", async () => {
await assert.rejects(
solveDeepSeekPowAsync("DeepSeekHashV1", "not-a-sha3-digest", "salt", 1, 1),
/challenge/i
);
await assert.rejects(
solveDeepSeekPowAsync(
"DeepSeekHashV1",
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
"salt",
-1,
1
),
/difficulty/i
);
await assert.rejects(
solveDeepSeekPowAsync("DeepSeekHashV1", noMatchChallenge, "salt", 250_001, 1),
/difficulty/i
);
assert.throws(
() => solveDeepSeekPow("DeepSeekHashV1", noMatchChallenge, "salt", 250_001, 1),
/difficulty/i
);
assert.throws(
() =>
solveDeepSeekPow(
"UnknownHash",
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
"salt",
1,
1
),
/unsupported/i
);
});
test("DeepSeek PoW sync and async APIs agree for non-zero and missing answers", async () => {
const nonceThreeChallenge = "2ffed26ea9e1d6f4bbe49a266d98fe04ad9a5ad4c6d765862de8d18c513c3815";
assert.equal(solveDeepSeekPow("DeepSeekHashV1", nonceThreeChallenge, "vector", 4, 42), 3);
assert.equal(
await solveDeepSeekPowAsync("DeepSeekHashV1", nonceThreeChallenge, "vector", 4, 42),
3
);
assert.equal(solveDeepSeekPow("DeepSeekHashV1", nonceThreeChallenge, "vector", 3, 42), -1);
assert.equal(
await solveDeepSeekPowAsync("DeepSeekHashV1", nonceThreeChallenge, "vector", 3, 42),
-1
);
});
test("DeepSeek PoW async search yields to timers and honors cancellation", async () => {
const controller = new AbortController();
let timerFired = false;
const timer = setTimeout(() => {
timerFired = true;
controller.abort();
}, 0);
try {
await assert.rejects(
solveDeepSeekPowAsync(
"DeepSeekHashV1",
noMatchChallenge,
"1122334455667788",
256,
1778891543095,
{ signal: controller.signal }
),
(error: unknown) => error instanceof Error && error.name === "AbortError"
);
assert.equal(timerFired, true, "the event loop must progress while PoW is being searched");
} finally {
clearTimeout(timer);
}
});
test("DeepSeek PoW async search enforces a deterministic timeout", async () => {
await assert.rejects(
solveDeepSeekPowAsync("DeepSeekHashV1", noMatchChallenge, "timeout", 250_000, 1, {
timeoutMs: 1,
}),
/exceeded 1ms/i
);
});
test("DeepSeek PoW caps concurrent worker searches at two", async () => {
const controllers = [new AbortController(), new AbortController()];
const searches = controllers.map((controller) =>
solveDeepSeekPowAsync("DeepSeekHashV1", noMatchChallenge, "capacity", 250_000, 1, {
signal: controller.signal,
timeoutMs: 30_000,
})
);
try {
await assert.rejects(
solveDeepSeekPowAsync("DeepSeekHashV1", noMatchChallenge, "capacity", 1, 1),
/capacity reached \(2\)/i
);
} finally {
for (const controller of controllers) controller.abort();
await Promise.allSettled(searches);
}
});

View File

@@ -1,21 +0,0 @@
import test from "node:test";
import assert from "node:assert/strict";
import { createRequire } from "node:module";
const requireCJS = createRequire(import.meta.url);
test("deepseek-pow-solver.cjs loads under Node strict mode without ReferenceError (#2724)", () => {
let mod: { U?: unknown };
assert.doesNotThrow(() => {
mod = requireCJS("../../open-sse/lib/deepseek-pow-solver.cjs");
});
assert.ok(mod!, "module loaded");
assert.equal(typeof mod!.U, "function", "expected exported constructor U");
});
test("deepseek-pow-solver.cjs does not pollute globalThis.onmessage in Node (#2724)", () => {
const before = (globalThis as { onmessage?: unknown }).onmessage;
requireCJS("../../open-sse/lib/deepseek-pow-solver.cjs");
const after = (globalThis as { onmessage?: unknown }).onmessage;
assert.equal(after, before, "onmessage must remain unchanged outside Worker context");
});

View File

@@ -648,24 +648,73 @@ test("execute with a new DeepSeek userToken restarts auto-refresh", async () =>
// ─── Abort handling ──────────────────────────────────────────────────────
test("execute: handles abort signal gracefully", async () => {
test("execute: propagates the request signal into an in-flight PoW search", async () => {
const dsMod4 = await import("../../open-sse/executors/deepseek-web.ts");
if (dsMod4.tokenCache) dsMod4.tokenCache.clear();
const executor = new DeepSeekWebExecutor();
dsMod4.tokenCache.clear();
dsMod4.sessionCache.clear();
const originalFetch = globalThis.fetch;
const controller = new AbortController();
controller.abort();
const result = await executor.execute({
model: "default",
body: { messages: [{ role: "user", content: "hi" }] },
stream: true,
credentials: { apiKey: "test-token-abort" },
signal: controller.signal,
});
assert.ok(result.response, "Should return response");
assert.ok(
result.response.status >= 400 || result.response.status === 499,
"Should indicate error or abort"
);
let completionCalls = 0;
globalThis.fetch = async (input) => {
const url = input.toString();
if (url.includes("/users/current")) {
return new Response(
JSON.stringify({ code: 0, data: { biz_data: { token: "abort-access-token" } } }),
{ status: 200, headers: { "Content-Type": "application/json" } }
);
}
if (url.includes("/chat_session/create")) {
return new Response(
JSON.stringify({
code: 0,
data: { biz_data: { chat_session: { id: "abort-session" } } },
}),
{ status: 200, headers: { "Content-Type": "application/json" } }
);
}
if (url.includes("/chat/create_pow_challenge")) {
setTimeout(() => controller.abort(), 0);
return new Response(
JSON.stringify({
code: 0,
data: {
biz_data: {
challenge: {
algorithm: "DeepSeekHashV1",
challenge: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
salt: "abort",
signature: "test-signature",
difficulty: 50_000,
expire_at: 1,
expire_after: 1,
target_path: "/api/v0/chat/completion",
},
},
},
}),
{ status: 200, headers: { "Content-Type": "application/json" } }
);
}
if (url.includes("/chat/completion")) completionCalls += 1;
throw new Error(`Unexpected request after PoW cancellation: ${url}`);
};
try {
const result = await new DeepSeekWebExecutor().execute({
model: "default",
body: { messages: [{ role: "user", content: "hi" }] },
stream: false,
credentials: { apiKey: "test-token-abort" },
signal: controller.signal,
});
assert.equal(result.response.status, 499);
assert.equal(completionCalls, 0, "an aborted request must not reach completion");
} finally {
globalThis.fetch = originalFetch;
dsMod4.tokenCache.clear();
dsMod4.sessionCache.clear();
}
});
// ─── Search enabled ──────────────────────────────────────────────────────