Files
kashilo/js/services/pow-captcha.js

57 lines
1.6 KiB
JavaScript

// Proof-of-Work Captcha Service
// Client must find nonce where SHA256(challenge + nonce) has N leading zeros
const DIFFICULTY = 4 // Number of leading zeros required (4 = ~65k attempts avg)
// TODO: Replace with a server-side endpoint. Currently generates challenge
// client-side with a btoa() "signature" that provides no real security.
export function generateChallenge() {
const challenge = crypto.randomUUID()
const timestamp = Date.now()
return {
challenge,
difficulty: DIFFICULTY,
timestamp,
signature: btoa(`${challenge}:${timestamp}:${DIFFICULTY}`)
}
}
// Solve challenge (runs in browser)
export async function solveChallenge(challenge, difficulty, onProgress) {
let nonce = 0
const prefix = '0'.repeat(difficulty)
const startTime = Date.now()
while (true) {
const hash = await sha256(`${challenge}${nonce}`)
if (hash.startsWith(prefix)) {
return {
nonce,
hash,
duration: Date.now() - startTime
}
}
nonce++
// Report progress every 1000 attempts
if (onProgress && nonce % 1000 === 0) {
onProgress({ attempts: nonce, elapsed: Date.now() - startTime })
}
// Yield to main thread every 100 attempts
if (nonce % 100 === 0) {
await new Promise(r => setTimeout(r, 0))
}
}
}
// SHA256 helper
async function sha256(message) {
const msgBuffer = new TextEncoder().encode(message)
const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer)
const hashArray = Array.from(new Uint8Array(hashBuffer))
return hashArray.map(b => b.toString(16).padStart(2, '0')).join('')
}