main
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<PowJob>) => 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') })
}