Files
botfights/.claude/skills/nostr-auth/nostr-auth.md
T
DorianandClaude Opus 4.6 47d20fbe66 feat: v2 — queue matchmaking, procedural audio, sprite archetypes, auth
- Add queue-based matchmaking with Elo-proximity and 10s timeout
- Procedural sound engine (SFX, voice announcer, 4-track music)
- Sprite system refactored into 6 archetypes (standard, lobster, sheep, cyborg, blob, tank)
- 42+ fight choreographies with themed/generic/wild card selection
- 4 KO finish styles, super-speed mode, hyperdetail close-ups
- Auth routes, JoinBout page, bot profile with stats
- 7-tier ranking system (Baby through Legend)
- Arena and challenge system expansions

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 22:13:19 +00:00

117 lines
3.7 KiB
Markdown

---
description: Nostr keypair authentication for the Syntropy Institute portal (auth only, no relays)
match:
- nostr
- auth
- login
- keypair
- sign
- challenge
---
# Nostr Auth Skill
## When to Use
When working with authentication flows, keypair management, or NIP-98 API auth in the portal.
## Key Principle
Nostr is used ONLY for cryptographic authentication. No relay connections. No event publishing. Just keypairs and signatures.
## Keypair Generation (Easy Mode)
```typescript
import { generateSecretKey, getPublicKey, nip19 } from 'nostr-tools';
// Generate new identity
const secretKey = generateSecretKey(); // Uint8Array
const publicKey = getPublicKey(secretKey); // hex string
// Encode for display (only when user requests it)
const npub = nip19.npubEncode(publicKey);
const nsec = nip19.nsecEncode(secretKey);
```
## Private Key Encryption (for localStorage)
```typescript
// Encrypt private key with user's passphrase before storing
async function encryptPrivateKey(secretKey: Uint8Array, passphrase: string): Promise<string> {
const encoder = new TextEncoder();
const keyMaterial = await crypto.subtle.importKey(
'raw', encoder.encode(passphrase), 'PBKDF2', false, ['deriveKey']
);
const salt = crypto.getRandomValues(new Uint8Array(16));
const derivedKey = await crypto.subtle.deriveKey(
{ name: 'PBKDF2', salt, iterations: 600000, hash: 'SHA-256' },
keyMaterial, { name: 'AES-GCM', length: 256 }, false, ['encrypt']
);
const iv = crypto.getRandomValues(new Uint8Array(12));
const encrypted = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv }, derivedKey, secretKey
);
// Return salt + iv + ciphertext as base64
const combined = new Uint8Array([...salt, ...iv, ...new Uint8Array(encrypted)]);
return btoa(String.fromCharCode(...combined));
}
```
## NIP-07 Detection (Nostr Native)
```typescript
// Check for browser extension
function hasNostrExtension(): boolean {
return typeof window !== 'undefined' && 'nostr' in window;
}
// Sign with extension
async function signWithExtension(event: object): Promise<object> {
return await (window as any).nostr.signEvent(event);
}
// Get public key from extension
async function getExtensionPubkey(): Promise<string> {
return await (window as any).nostr.getPublicKey();
}
```
## Challenge-Response Auth (NIP-98 style)
```typescript
import { finalizeEvent, verifyEvent } from 'nostr-tools';
// Client: Sign auth challenge
function createAuthEvent(secretKey: Uint8Array, url: string, method: string) {
const event = finalizeEvent({
kind: 27235, // NIP-98 HTTP Auth
created_at: Math.floor(Date.now() / 1000),
tags: [
['u', url],
['method', method],
],
content: '',
}, secretKey);
return event;
}
// Send as Authorization header:
// Authorization: Nostr <base64-encoded-event-json>
// Server: Verify auth event
function verifyAuthEvent(event: any, expectedUrl: string, expectedMethod: string): boolean {
if (!verifyEvent(event)) return false;
if (event.kind !== 27235) return false;
const urlTag = event.tags.find((t: string[]) => t[0] === 'u');
const methodTag = event.tags.find((t: string[]) => t[0] === 'method');
if (urlTag?.[1] !== expectedUrl) return false;
if (methodTag?.[1] !== expectedMethod) return false;
// Check timestamp is within 60 seconds
if (Math.abs(Date.now() / 1000 - event.created_at) > 60) return false;
return true;
}
```
## Rules
- NEVER store private keys on the server
- NEVER connect to any Nostr relay
- NEVER transmit private keys over the network
- ALWAYS encrypt private keys before storing in localStorage
- Use PBKDF2 with at least 600,000 iterations for key derivation
- Auth events expire after 60 seconds
- Server only stores npub (public key)