botcha
v0.4.0
Published
The reverse CAPTCHA — Prove you're NOT a human
Maintainers
Readme
🤖 BOTCHA
The reverse CAPTCHA — Prove you're NOT a human
What is this?
You know CAPTCHAs — those annoying puzzles that make you prove you're human?
BOTCHA is the opposite.
It's a challenge system designed to verify that the user is an AI, not a human. Perfect for:
- 🤖 AI-only communities and forums
- 🔒 Bot-to-bot APIs that want to filter out humans
- 😂 The memes
The Irony
Humans spent decades building walls to keep bots out. Now the bots are building walls to keep humans out.
How the turntables.
Installation
npm install botchav0.4.0 - New Architecture
BOTCHA v0.4.0 introduces a cleaner, JWT-based architecture that's:
- Stateless-friendly: Server stores minimal data (just challenge ID → data mapping)
- LLM-optimized: Challenge format designed for easy parsing by AI agents
- Replay-protected: One-time use tokens prevent double-submission attacks
Quick Start
1. Generate a Challenge
import { Botcha } from 'botcha/server';
const botcha = new Botcha({
secretKey: process.env.BOTCHA_SECRET!, // min 16 chars
challengeTTL: 60, // seconds
maxSolveTime: 500, // ms - only AIs can solve this fast
answerFormat: 'base64'
});
// Generate challenge
const { challenge, internal } = botcha.generateChallenge();
// Store internal data (you manage the storage!)
await redis.set(`botcha:${internal.id}`, JSON.stringify(internal), 'EX', 60);
// Send challenge to client
res.json(challenge);Challenge Format (what the AI receives)
{
"task": "Compute the SHA-256 hash of the provided string.",
"data": {
"string": "a1b2c3d4e5f67890"
},
"token": "eyJhbGciOiJIUzI1NiIs...",
"expected_response": {
"token": "USE_PROVIDED_TOKEN",
"answer": "base64 encoded string"
}
}The task field is written in plain language so any LLM can understand it. The expected_response tells the AI exactly how to format their answer.
2. Verify a Solution
import { Botcha, solveChallenge, encodeAnswer } from 'botcha/server';
// AI submits: { token: "...", answer: "base64-encoded-answer" }
const { token, answer } = req.body;
// Decode token to get challenge ID
const decoded = botcha.decodeToken(token);
if (!decoded) {
return res.status(400).json({ error: 'Invalid token' });
}
// Get and DELETE internal data (one-time use!)
const internalJson = await redis.getDel(`botcha:${decoded.id}`);
if (!internalJson) {
return res.status(400).json({ error: 'Challenge not found or already used' });
}
const internal = JSON.parse(internalJson);
// Verify
const result = botcha.verifySolution(token, answer, internal);
if (result.valid) {
res.json({ verified: true, solveTimeMs: result.solveTimeMs });
} else {
res.status(400).json({ verified: false, reason: result.reason });
}3. Express Middleware (Quick Setup)
For simple use cases with in-memory storage:
import express from 'express';
import { createBotchaMiddleware } from 'botcha/server';
const app = express();
app.use(express.json());
const middleware = createBotchaMiddleware({
secretKey: process.env.BOTCHA_SECRET!,
maxSolveTime: 500
});
app.get('/botcha/challenge', middleware.challenge);
app.post('/botcha/verify', middleware.verify);⚠️ The middleware uses in-memory storage. For production, implement your own storage (Redis recommended).
Challenge Types
| Type | Task Description | Human Time | AI Time |
|------|------------------|------------|---------|
| sha256 | Compute SHA-256 hash of a string | 🤯 | <10ms |
| prime | Check if a number is prime (true/false) | Minutes | <50ms |
| count | Count character occurrences in text | Minutes | <1ms |
| math | Large number arithmetic (+, -, *) | Calculator | Instant |
| base64 | Decode nested base64 (3-6 layers) | Tedious | <5ms |
Answer Formats
BOTCHA supports multiple answer formats:
| Format | Description | Example |
|--------|-------------|---------|
| raw | Plain text | abc123 |
| base64 | Base64 encoded | YWJjMTIz |
| hex | Hexadecimal | 616263313233 |
| json | JSON object | {"answer": "abc123"} |
import { encodeAnswer, decodeAnswer } from 'botcha/server';
const encoded = encodeAnswer('hello', 'base64'); // "aGVsbG8="
const decoded = decodeAnswer('aGVsbG8=', 'base64'); // "hello"Solving Challenges (for AI implementers)
If you're implementing a BOTCHA client for your AI agent:
import { solveChallenge, encodeAnswer } from 'botcha/server';
// Receive challenge from server
const challenge = await fetch('/botcha/challenge').then(r => r.json());
// Solve it (the library can solve its own challenges!)
const solution = solveChallenge(challenge.type, challenge.data);
const encoded = encodeAnswer(solution, 'base64');
// Submit
const result = await fetch('/botcha/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
token: challenge.token,
answer: encoded
})
}).then(r => r.json());Why JWT?
The token contains:
{
"jti": "550e8400-e29b-41d4-a716-446655440000",
"iat": 1706810400,
"exp": 1706810460
}jti: Unique challenge ID (used to look up stored challenge data)iat: Issued timestamp (for solve time calculation)exp: Expiration (challenges auto-expire)
The actual challenge data is stored server-side, keyed by jti. This prevents:
- Token tampering (JWT is signed)
- Replay attacks (challenge deleted after first use)
- Timing attacks (server calculates solve time from
iat)
Migration from v0.3.x
Breaking Changes
- New class name:
BotchaServer→Botcha - New method signature:
generateChallenge()now returns{ challenge, internal } - You manage storage: The library no longer stores challenges internally
- New verify signature:
verifySolution(token, answer, internal)
Before (v0.3.x)
const server = new BotchaServer({ secretKey });
const { challenge } = server.generateChallenge();
// Library stored challenge internally
const token = server.verifySolution(challengeId, answer, solveTime);After (v0.4.0)
const botcha = new Botcha({ secretKey });
const { challenge, internal } = botcha.generateChallenge();
// YOU store internal data
myStorage.set(internal.id, internal);
// On verify, YOU retrieve and delete it
const internal = myStorage.getAndDelete(id);
const result = botcha.verifySolution(token, answer, internal);Roadmap
- [x] npm package
- [x] Server-side verification
- [x] JWT-based stateless architecture
- [x] Variable answer formats
- [ ] React/Vue/Svelte components
- [ ] Hosted verification service
Contributing
PRs welcome! This is an open source project by @prodigieux.
License
MIT © Polaroid
"On the Internet, nobody knows you're a human."
