Перед каждым API-запросом клиент решает вычислительную задачу (100-500ms CPU). Сервер проверяет решение за микросекунды. Единственная техника, которая линейно масштабирует стоимость скрейпинга с количеством запросов.

Принцип

1. Клиент запрашивает challenge: GET /api/challenge
2. Сервер отдаёт: { challenge: "abc123", difficulty: 4 }
3. Клиент в Web Worker перебирает nonce:
   SHA256(challenge + nonce) должен начинаться с N нулей
4. Клиент отправляет API-запрос с решением в заголовке
5. Сервер верифицирует за микросекунды (одно SHA256)
6. Challenge одноразовый — replay невозможен

Экономика

ДействиеСтоимость
1 запрос~200ms CPU клиента
1000 запросов~200 секунд CPU
Полный дамп (10 000 запросов)~33 минуты CPU

Для обычного пользователя (10-50 запросов за сессию) — незаметно. Для скрапера — дорого. Масштабирование бот-фермы требует пропорционального увеличения CPU.

ALTCHA

Self-hosted, open-source, GDPR-friendly альтернатива reCAPTCHA и Cloudflare Turnstile. Нет зависимости от третьих сторон.

  • npm: altcha (сервер) + altcha Web Component (клиент)
  • Верификация: ~50 строк серверного кода
  • Клиент: Web Component <altcha-widget> или программный API
  • Лицензия: MIT

ALTCHA vs Cloudflare Turnstile

ALTCHATurnstile
ХостингSelf-hostedCloudflare
GDPRПолный контрольДанные у Cloudflare
СтоимостьБесплатноБесплатно
СтойкостьСредняя (PoW)Средняя-высокая (ML)
ОфлайнРаботаетНет

Реализация

Клиент — Web Worker для неблокирующего решения:

// В Web Worker
self.onmessage = ({ data: { challenge, difficulty } }) => {
  let nonce = 0
  while (true) {
    const hash = sha256(challenge + nonce)
    if (hash.startsWith('0'.repeat(difficulty))) {
      self.postMessage({ nonce, hash })
      return
    }
    nonce++
  }
}

Сервер — верификация:

app.addHook('onRequest', (req, reply, done) => {
  const solution = req.headers['x-pow-solution']
  const challenge = req.headers['x-pow-challenge']
  if (!verifyPoW(challenge, solution)) {
    return reply.code(403).send({ error: 'Invalid proof of work' })
  }
  done()
})

Когда применять

PoW добавляет 100-500ms задержку — не на каждый запрос. Стратегии:

СтратегияКогда PoW
Только публичные спискиGET без авторизации
AdaptiveПри suspicion score > порога
Первый запрос в сессииОдин раз, потом cookie на 15 мин
Пагинация за N-й страницейoffset > 100

Для авторизованных пользователей — не нужен (они уже доказали, что не боты).

Ограничения

  • Задержка 100-500ms ощутима для UX (хотя и в фоне)
  • Web Worker не работает в IE11 (но уже неактуально)
  • Бот-ферма с GPU может решать PoW быстрее — сложность нужно калибровать
  • Не защищает от headless Chrome (он решает PoW как обычный браузер)

Связанные заметки