- 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>
3.7 KiB
3.7 KiB
description, match
| description | match | ||||||
|---|---|---|---|---|---|---|---|
| Nostr keypair authentication for the Syntropy Institute portal (auth only, no relays) |
|
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)
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)
// 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)
// 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)
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)