@logscore/botcha
v1.1.1
Published
LLM verification through logic-based challenges
Maintainers
Readme
Botcha
AI verification through logic-based challenges. Botcha generates complex ownership-transfer puzzles that are trivial for LLMs to solve but difficult for humans or scripts to solve in time.
Installation
npm install @logscore/botchaLLM Quick Start
To get your coding gent up to speed as fast as possible, we recommend asking it to read node_modules/@logscore/botcha/skills.md and follow the instructions.
Optional Storage Adapters
You can use any storage adapter that implements the StorageAdapter interface. By default, Botcha uses an in-memory storage adapter. FOr production, we recommend using Redis or PostgreSQL.
Quick Start
import { createBotcha } from "@logscore/botcha";
const botcha = createBotcha();
// Create a challenge
const { id, text } = await botcha.createChallenge();
console.log(text);
// Output: CUSTODY CHAIN: SILVER LOCKET
// Track possession of the silver locket through the following sequence.
//
// 1. Morgan left the silver locket in a secure container.
// 2. Quinn discovered the missing silver locket under a desk.
// ... (50 events)
//
// QUESTION: Who possesses the silver locket at the conclusion of event 23?
// Verify the answer
const isHuman = await botcha.verifyChallenge(id, "Riley");
console.log(isHuman); // true or falseConfiguration
Basic Configuration
import { createBotcha } from "@logscore/botcha";
const botcha = createBotcha({
// Challenge generation options
challengeConfig: {
people: ["Alex", "Jordan", "Taylor"], // Custom names (default: 7 names)
objects: ["red key", "blue orb"], // Custom objects (default: 4 objects)
transactionCount: 30, // Number of events (default: 50)
enableDistractors: true, // Add distractor notes (default: true)
},
// Session management
expirationMs: 60000, // 60 seconds (default: 30 seconds)
// Background sweeper (optional)
sweeperIntervalMs: 60000, // Cleanup every 60 seconds
});Storage Adapters
In-Memory (Default)
import { createBotcha } from "@logscore/botcha";
const botcha = createBotcha(); // Uses memory storage by defaultRedis
import { createBotcha, RedisStorage } from "@logscore/botcha";
const botcha = createBotcha({
storage: new RedisStorage({
url: "redis://localhost:6379",
keyPrefix: "myapp:botcha:",
}),
});PostgreSQL
import { createBotcha, PostgresStorage } from "@logscore/botcha";
const botcha = createBotcha({
storage: new PostgresStorage({
connectionString: process.env.DATABASE_URL,
}),
});SQLite
import { createBotcha, SQLiteStorage } from "@logscore/botcha";
const botcha = createBotcha({
storage: new SQLiteStorage({
path: "./botcha.db", // Or ':memory:' for in-memory
}),
});MySQL
import { createBotcha, MySQLStorage } from "@logscore/botcha";
const botcha = createBotcha({
storage: new MySQLStorage({
host: "localhost",
port: 3306,
user: "root",
password: "password",
database: "myapp",
}),
});Custom Storage Adapter
import { createBotcha, type StorageAdapter, type Session } from "@logscore/botcha";
class DynamoDBStorage implements StorageAdapter {
async get(id: string): Promise<Session | null> {
// Your implementation
}
async set(id: string, session: Session): Promise<void> {
// Your implementation
}
async delete(id: string): Promise<boolean> {
// Your implementation
}
async cleanupExpired(): Promise<number> {
// Your implementation
}
}
const botcha = createBotcha({
storage: new DynamoDBStorage(),
});Error Handling
import {
Botcha,
ChallengeNotFoundError,
ChallengeExpiredError,
InvalidAnswerError,
} from "@logscore/botcha";
const botcha = createBotcha();
try {
const isHuman = await botcha.verifyChallenge(id, answer);
} catch (error) {
if (error instanceof ChallengeNotFoundError) {
// Challenge doesn't exist or already used
} else if (error instanceof ChallengeExpiredError) {
// Challenge timed out
} else if (error instanceof InvalidAnswerError) {
// Wrong answer
}
}Framework Examples
Express.js
import express from "express";
import { createBotcha } from "@logscore/botcha";
const app = express();
const botcha = createBotcha();
app.post("/challenge", async (req, res) => {
const { id, text } = await botcha.createChallenge();
res.json({ id, text });
});
app.post("/verify", async (req, res) => {
const { id, answer } = req.body;
try {
const isHuman = await botcha.verifyChallenge(id, answer);
res.json({ isHuman });
} catch (error) {
res.status(400).json({ error: error.message });
}
});Next.js API Route
import { createBotcha } from "@logscore/botcha";
const botcha = createBotcha();
export async function POST() {
const { id, text } = await botcha.createChallenge();
return Response.json({ id, text });
}Hono
import { Hono } from "hono";
import { createBotcha } from "@logscore/botcha";
const app = new Hono();
const botcha = createBotcha();
app.post("/challenge", async (c) => {
const { id, text } = await botcha.createChallenge();
return c.json({ id, text });
});Cleanup Strategies
Lazy Cleanup (Default)
Expired sessions are removed only when accessed. Zero background overhead.
const botcha = createBotcha(); // Lazy cleanup onlyBackground Sweeper
Periodic cleanup for high-traffic applications.
const botcha = createBotcha({
sweeperIntervalMs: 60000, // Cleanup every 60 seconds
});
// Stop sweeper when shutting down
botcha.stopSweeper();Manual Cleanup
const deleted = await botcha.cleanupExpired();
console.log(`Cleaned up ${deleted} expired sessions`);Security
- Timing-safe comparison: Uses
crypto.timingSafeEqualto prevent timing attacks - Short-lived sessions: 30-second default expiration prevents replay attacks
- Cryptographic RNG: SHA-256 based deterministic RNG
- One-time use: Sessions are deleted after successful verification
Database Schema
PostgreSQL/SQLite/MySQL
CREATE TABLE botcha_sessions (
id VARCHAR(21) PRIMARY KEY,
answer VARCHAR(255) NOT NULL,
seed VARCHAR(255) NOT NULL,
challenge_type VARCHAR(50) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
expires_at TIMESTAMP NOT NULL
);
CREATE INDEX idx_expires ON botcha_sessions(expires_at);Redis
Uses hash data structure with automatic TTL:
HSET botcha:session:<id> answer <answer> seed <seed> challenge_type <type> expires_at <timestamp>
EXPIRE botcha:session:<id> <ttl_seconds>API Reference
Botcha Class
Constructor
createBotcha(config?: BotchaConfig)Methods
createChallenge(): Promise<ChallengeResponse>- Create a new challengeverifyChallenge(id: string, answer: string): Promise<boolean>- Verify an answergetSession(id: string): Promise<Session | null>- Get session infodeleteChallenge(id: string): Promise<boolean>- Delete a challengecleanupExpired(): Promise<number>- Manually cleanup expired sessionsstopSweeper(): void- Stop background sweeper
Types
interface BotchaConfig {
challengeType?: "ownership";
challengeConfig?: OwnershipConfig;
storage?: StorageAdapter;
expirationMs?: number;
sweeperIntervalMs?: number;
}
interface OwnershipConfig {
people?: string[];
objects?: string[];
transactionCount?: number;
questionEventMin?: number;
questionEventMax?: number;
enableDistractors?: boolean;
}
interface ChallengeResponse {
id: string;
text: string;
}
interface Session {
id: string;
answer: string;
seed: string;
challengeType: string;
expiresAt: number;
}License
MIT
