Files
botfights/.claude/skills/encrypted-records/encrypted-records.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

4.3 KiB

description, match
description match
E2E encrypted records and group encryption for the Syntropy Institute portal
encrypt
decrypt
record
NIP-44
group key
channel
community

Encrypted Records Skill

When to Use

When working with E2E encrypted client records, community posts, or group key management.

NIP-44 Encryption (Practitioner <-> Client)

import { nip44 } from 'nostr-tools';

// Encrypt record for a specific client
function encryptRecord(
  practitionerSecretKey: Uint8Array,
  clientPubKey: string,
  recordData: object
): string {
  const plaintext = JSON.stringify(recordData);
  const conversationKey = nip44.v2.utils.getConversationKey(practitionerSecretKey, clientPubKey);
  return nip44.v2.encrypt(plaintext, conversationKey);
}

// Decrypt record (client side)
function decryptRecord(
  clientSecretKey: Uint8Array,
  practitionerPubKey: string,
  encryptedData: string
): object {
  const conversationKey = nip44.v2.utils.getConversationKey(clientSecretKey, practitionerPubKey);
  const plaintext = nip44.v2.decrypt(encryptedData, conversationKey);
  return JSON.parse(plaintext);
}

Group Encryption (Community Channels)

Creating a Channel with Group Key

// Generate a symmetric group key for the channel
function generateGroupKey(): Uint8Array {
  return crypto.getRandomValues(new Uint8Array(32));
}

// Encrypt group key for a specific member using NIP-44
function wrapGroupKeyForMember(
  adminSecretKey: Uint8Array,
  memberPubKey: string,
  groupKey: Uint8Array
): string {
  const conversationKey = nip44.v2.utils.getConversationKey(adminSecretKey, memberPubKey);
  return nip44.v2.encrypt(
    btoa(String.fromCharCode(...groupKey)),
    conversationKey
  );
}

// Member unwraps their group key
function unwrapGroupKey(
  memberSecretKey: Uint8Array,
  adminPubKey: string,
  wrappedKey: string
): Uint8Array {
  const conversationKey = nip44.v2.utils.getConversationKey(memberSecretKey, adminPubKey);
  const decoded = nip44.v2.decrypt(wrappedKey, conversationKey);
  return Uint8Array.from(atob(decoded), c => c.charCodeAt(0));
}

Encrypting Community Posts with Group Key

// Encrypt post content with the channel's symmetric group key
async function encryptPostWithGroupKey(
  groupKey: Uint8Array,
  content: string
): Promise<string> {
  const key = await crypto.subtle.importKey(
    'raw', groupKey, 'AES-GCM', false, ['encrypt']
  );
  const iv = crypto.getRandomValues(new Uint8Array(12));
  const encoded = new TextEncoder().encode(content);
  const ciphertext = await crypto.subtle.encrypt(
    { name: 'AES-GCM', iv }, key, encoded
  );
  const combined = new Uint8Array([...iv, ...new Uint8Array(ciphertext)]);
  return btoa(String.fromCharCode(...combined));
}

// Decrypt post content
async function decryptPostWithGroupKey(
  groupKey: Uint8Array,
  encrypted: string
): Promise<string> {
  const combined = Uint8Array.from(atob(encrypted), c => c.charCodeAt(0));
  const iv = combined.slice(0, 12);
  const ciphertext = combined.slice(12);
  const key = await crypto.subtle.importKey(
    'raw', groupKey, 'AES-GCM', false, ['decrypt']
  );
  const decrypted = await crypto.subtle.decrypt(
    { name: 'AES-GCM', iv }, key, ciphertext
  );
  return new TextDecoder().decode(decrypted);
}

Key Rotation (when member removed)

// When a member is removed from a channel:
// 1. Generate new group key
// 2. Re-wrap for all remaining members
// 3. Update ChannelMember.encryptedGroupKey for each
// 4. New posts use new key; old posts remain readable with old key
//    (store key version/epoch on each post)

Record Types

interface ClientRecord {
  type: 'session_notes' | 'assessment' | 'treatment_plan';
  title: string;
  content: string;  // Rich text or structured data
  attachments?: {
    name: string;
    mimeType: string;
    encryptedData: string;  // Each file encrypted separately
  }[];
  createdAt: string; // ISO timestamp
}

Rules

  • ALL encryption happens client-side — server stores only encrypted blobs
  • Use NIP-44 v2 (NOT NIP-04 — it's deprecated and has known weaknesses)
  • Group keys: AES-256-GCM with random IVs
  • Include key version/epoch on group-encrypted content for rotation support
  • Never log or expose plaintext content on the server
  • Media files are encrypted individually before upload