import type { ChallengeResponse } from '@/shared/api' import { sha256 } from '@noble/hashes/sha2.js' import { t } from '@/shared/i18n' type PowJob = { challenge: ChallengeResponse; payload: string } const scope = globalThis as unknown as { onmessage: ((event: MessageEvent) => void) | null postMessage: (message: { nonce: number } | { error: string }) => void } scope.onmessage = ({ data: { challenge, payload } }) => { if (!Number.isInteger(challenge.difficulty) || challenge.difficulty < 0 || challenge.difficulty > 256) { scope.postMessage({ error: t('pow.invalid') }) return } const encoder = new TextEncoder() const prefix = encoder.encode(`pow.v1\n${challenge.context}\0`) const bytes = encoder.encode(payload) const buffer = new Uint8Array(prefix.length + 4 + 1 + 8 + 1 + 8 + 1 + 8 + bytes.length + 1 + 8) buffer.set(prefix) const view = new DataView(buffer.buffer) let offset = prefix.length view.setUint32(offset, challenge.difficulty, false) offset += 5 view.setBigUint64(offset, BigInt(challenge.issued_at), false) offset += 9 view.setBigUint64(offset, BigInt(challenge.expires_at), false) offset += 9 view.setBigUint64(offset, BigInt(bytes.length), false) offset += 8 buffer.set(bytes, offset) offset += bytes.length + 1 const full = Math.floor(challenge.difficulty / 8) const rest = challenge.difficulty % 8 const deadline = Math.min(challenge.expires_at * 1000, Date.now() + 75_000) for (let nonce = 0; nonce <= Number.MAX_SAFE_INTEGER; nonce++) { if (nonce % 1024 === 0 && Date.now() >= deadline) { scope.postMessage({ error: t('pow.timeout') }) return } view.setBigUint64(offset, BigInt(nonce), false) const hash = sha256(buffer) let valid = true for (let index = 0; index < full; index++) { if (hash[index] !== 0) { valid = false break } } if (valid && (!rest || (hash[full] & (255 << (8 - rest))) === 0)) { scope.postMessage({ nonce }) return } } scope.postMessage({ error: t('pow.failed') }) }