@orangecheck/sdk 1.5.0 → 1.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/utils/logger.ts","../src/utils/rate-limiter.ts","../src/identity/dns.ts","../src/identity/github.ts","../src/nostr.ts","../src/identity/nostr.ts","../src/identity/twitter.ts","../src/identity/index.ts"],"names":["log","bech32","sha256","schnorr"],"mappings":"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","file":"identity.js","sourcesContent":["/**\n * Simple logger for SDK\n * Can be disabled in production builds\n */\n\n/**\n * Log level type\n */\nexport type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'none';\n\n/**\n * Logger context type\n */\nexport type LogContext = Record<string, unknown>;\n\n/**\n * Logger interface\n */\nexport interface Logger {\n debug: (contextOrMessage: string | LogContext, message?: string) => void;\n info: (contextOrMessage: string | LogContext, message?: string) => void;\n warn: (contextOrMessage: string | LogContext, message?: string) => void;\n error: (contextOrMessage: string | LogContext, message?: string) => void;\n}\n\nlet logLevel: LogLevel = 'none';\n\n/**\n * Set the global log level\n * @param level - Log level to set\n */\nexport function setLogLevel(level: LogLevel): void {\n logLevel = level;\n}\n\n/**\n * Get the current log level\n * @returns Current log level\n */\nexport function getLogLevel(): LogLevel {\n return logLevel;\n}\n\n/**\n * Create a namespaced logger\n * @param namespace - Logger namespace\n * @returns Logger instance\n */\nexport function createLogger(namespace: string): Logger {\n const shouldLog = (level: Exclude<LogLevel, 'none'>): boolean => {\n if (logLevel === 'none') return false;\n\n const levels: Exclude<LogLevel, 'none'>[] = ['debug', 'info', 'warn', 'error'];\n const currentLevelIndex = levels.indexOf(logLevel as Exclude<LogLevel, 'none'>);\n const messageLevelIndex = levels.indexOf(level);\n\n return messageLevelIndex >= currentLevelIndex;\n };\n\n const formatMessage = (msg: string): string => `[${namespace}] ${msg}`;\n\n return {\n debug: (contextOrMessage: string | LogContext, message?: string): void => {\n if (shouldLog('debug')) {\n if (typeof contextOrMessage === 'string') {\n console.debug(formatMessage(contextOrMessage));\n } else {\n console.debug(formatMessage(message ?? 'Debug'), contextOrMessage);\n }\n }\n },\n info: (contextOrMessage: string | LogContext, message?: string): void => {\n if (shouldLog('info')) {\n if (typeof contextOrMessage === 'string') {\n console.info(formatMessage(contextOrMessage));\n } else {\n console.info(formatMessage(message ?? 'Info'), contextOrMessage);\n }\n }\n },\n warn: (contextOrMessage: string | LogContext, message?: string): void => {\n if (shouldLog('warn')) {\n if (typeof contextOrMessage === 'string') {\n console.warn(formatMessage(contextOrMessage));\n } else {\n console.warn(formatMessage(message ?? 'Warning'), contextOrMessage);\n }\n }\n },\n error: (contextOrMessage: string | LogContext, message?: string): void => {\n if (shouldLog('error')) {\n if (typeof contextOrMessage === 'string') {\n console.error(formatMessage(contextOrMessage));\n } else {\n console.error(formatMessage(message ?? 'Error'), contextOrMessage);\n }\n }\n },\n };\n}\n\n/**\n * Log verification error\n * @param protocol - Identity protocol\n * @param identifier - Identity identifier\n * @param error - Error object\n * @param context - Additional context\n */\nexport function logVerificationError(\n protocol: string,\n identifier: string,\n error: Error,\n context?: LogContext\n): void {\n const logger = createLogger('identity-verification');\n logger.error(\n {\n protocol,\n identifier,\n error: error.message,\n stack: error.stack,\n ...context,\n },\n 'Verification failed'\n );\n}\n","/**\n * Simple in-memory rate limiter\n */\n\n/**\n * Rate limiter configuration\n */\nexport interface RateLimiter {\n /** Maximum number of requests allowed in the time window */\n readonly maxRequests: number;\n /** Time window in milliseconds */\n readonly windowMs: number;\n /** Internal request tracking map */\n readonly requests: Map<string, number[]>;\n}\n\n/**\n * Rate limit error\n */\nexport class RateLimitError extends Error {\n constructor(message = 'Rate limit exceeded. Please try again later.') {\n super(message);\n this.name = 'RateLimitError';\n }\n}\n\n/**\n * Create a rate limiter\n * @param maxRequests - Maximum requests allowed in the time window\n * @param windowMs - Time window in milliseconds\n * @returns Rate limiter instance\n */\nexport function createRateLimiter(maxRequests: number, windowMs: number): RateLimiter {\n if (maxRequests <= 0) {\n throw new Error('maxRequests must be greater than 0');\n }\n if (windowMs <= 0) {\n throw new Error('windowMs must be greater than 0');\n }\n\n return {\n maxRequests,\n windowMs,\n requests: new Map<string, number[]>(),\n };\n}\n\n/**\n * Check if a request is within rate limits\n * @param limiter - Rate limiter instance\n * @param key - Unique key for the request (e.g., user ID, IP address)\n * @returns True if request is allowed, false if rate limit exceeded\n */\nexport function checkRateLimit(limiter: RateLimiter, key: string): boolean {\n const now = Date.now();\n const requests = limiter.requests.get(key) ?? [];\n\n // Remove old requests outside the window\n const validRequests = requests.filter((timestamp) => now - timestamp < limiter.windowMs);\n\n // Check if limit exceeded\n if (validRequests.length >= limiter.maxRequests) {\n return false;\n }\n\n // Add new request\n validRequests.push(now);\n limiter.requests.set(key, validRequests);\n\n return true;\n}\n\n/**\n * Enforce rate limit, throwing an error if exceeded\n * @param limiter - Rate limiter instance\n * @param key - Unique key for the request\n * @throws {RateLimitError} If rate limit is exceeded\n */\nexport function enforceRateLimit(limiter: RateLimiter, key: string): void {\n if (!checkRateLimit(limiter, key)) {\n throw new RateLimitError();\n }\n}\n\n/**\n * Get remaining requests for a key\n * @param limiter - Rate limiter instance\n * @param key - Unique key for the request\n * @returns Number of remaining requests in the current window\n */\nexport function getRemainingRequests(limiter: RateLimiter, key: string): number {\n const now = Date.now();\n const requests = limiter.requests.get(key) ?? [];\n const validRequests = requests.filter((timestamp) => now - timestamp < limiter.windowMs);\n return Math.max(0, limiter.maxRequests - validRequests.length);\n}\n\n/**\n * Reset rate limit for a key\n * @param limiter - Rate limiter instance\n * @param key - Unique key to reset\n */\nexport function resetRateLimit(limiter: RateLimiter, key: string): void {\n limiter.requests.delete(key);\n}\n\n/**\n * Default rate limiter for identity verification\n * 5 requests per minute\n */\nexport const identityVerificationLimiter = createRateLimiter(5, 60000);\n","/**\n * DNS Identity Verification\n * Verifies that a user controls a DNS domain\n */\n\nimport { createLogger } from '../utils/logger';\n\nconst log = createLogger('identity-verification/dns');\n\n/** Public-internet check. Blocks literal private/loopback/link-local hosts\n * from being reached via the well-known file verifier — otherwise a caller\n * running this server-side is an SSRF primitive (cloud metadata, internal\n * services, etc.). We don't do full DNS resolution here — TLD/label shape\n * checks + literal-IP rejection are the cheap layers. Production verifiers\n * running this untrusted should add DNS-pinning on top.\n */\nconst PRIVATE_HOST_RE =\n /^(?:localhost|.*\\.local|.*\\.internal|.*\\.localhost|.*\\.lan|.*\\.home|.*\\.arpa|\\[?::1\\]?|\\[?::\\]?)$/i;\nconst PRIVATE_IPV4_RE =\n /^(?:10\\.|127\\.|169\\.254\\.|192\\.168\\.|172\\.(?:1[6-9]|2\\d|3[01])\\.|0\\.|100\\.(?:6[4-9]|[7-9]\\d|1[01]\\d|12[0-7])\\.|22[4-9]\\.|23\\d\\.|24\\d\\.|25[0-5]\\.)/;\n\n/** Tight fetch deadline so a slow target can't stall the verifier. */\nconst WELL_KNOWN_TIMEOUT_MS = 5_000;\n\nfunction isSafePublicHost(host: string): boolean {\n const h = host.toLowerCase();\n if (PRIVATE_HOST_RE.test(h)) return false;\n if (PRIVATE_IPV4_RE.test(h)) return false;\n // IPv6 literal — we don't enumerate every private range; reject bracketed\n // literals entirely (no legitimate verification target is an IPv6 literal).\n if (h.startsWith('[') || /^[0-9a-f:]+$/.test(h)) return false;\n return true;\n}\n\n/**\n * Verification result for DNS identity\n */\nexport interface DnsVerificationResult {\n verified: boolean;\n method?: 'well-known' | 'dns-txt';\n url?: string;\n error?: string;\n}\n\n/**\n * Verify that a user controls a DNS domain\n *\n * Verification methods (in order of preference):\n * 1. Check .well-known/orangecheck.txt file\n * 2. Check DNS TXT record (requires server-side DNS lookup)\n *\n * @param attestationId - The attestation ID to look for\n * @param domain - The domain name (without protocol)\n * @returns Verification result\n */\nexport async function verifyDnsIdentity(\n attestationId: string,\n domain: string\n): Promise<DnsVerificationResult> {\n log.info({ attestationId, domain }, 'Verifying DNS identity');\n\n // Clean domain (remove protocol if present)\n const cleanDomain = domain.replace(/^https?:\\/\\//, '').replace(/\\/$/, '');\n\n // Shape + SSRF guards before we ever hand this to fetch().\n if (!isValidDomain(cleanDomain)) {\n return { verified: false, error: `invalid domain format: ${cleanDomain}` };\n }\n if (!isSafePublicHost(cleanDomain)) {\n return { verified: false, error: 'domain resolves to a disallowed private host' };\n }\n\n // Try .well-known file first\n const wellKnownResult = await checkWellKnownFile(attestationId, cleanDomain);\n if (wellKnownResult.verified) {\n return wellKnownResult;\n }\n\n // DNS TXT record check requires server-side implementation\n // For now, return instructions\n log.info({ domain: cleanDomain }, 'Well-known file not found, DNS TXT check not implemented');\n\n return {\n verified: false,\n error: 'Verification file not found. Please create .well-known/orangecheck.txt',\n };\n}\n\n/**\n * Check .well-known/orangecheck.txt file\n */\nasync function checkWellKnownFile(\n attestationId: string,\n domain: string\n): Promise<DnsVerificationResult> {\n // HTTPS only. The old HTTP fallback meant a MITM / hostile network could\n // forge a passing verification; dropping it costs us nothing — any real\n // domain worth attesting to has TLS in 2026.\n const url = `https://${domain}/.well-known/orangecheck.txt`;\n\n try {\n log.info({ url }, 'Checking well-known file');\n\n const response = await fetch(url, {\n method: 'GET',\n headers: { Accept: 'text/plain' },\n // Don't follow redirects to other domains\n redirect: 'manual',\n signal: AbortSignal.timeout(WELL_KNOWN_TIMEOUT_MS),\n });\n\n if (response.ok) {\n const content = await response.text();\n\n if (content.includes(attestationId)) {\n log.info({ domain, url }, 'Found attestation ID in well-known file');\n return {\n verified: true,\n method: 'well-known',\n url,\n };\n } else {\n log.info({ domain, url }, 'Well-known file exists but no attestation ID found');\n }\n }\n } catch (err) {\n log.warn({ error: err, url }, 'Failed to fetch well-known file');\n }\n\n return {\n verified: false,\n error: 'Well-known file not found or does not contain attestation ID',\n };\n}\n\n/**\n * Get verification instructions for DNS identity\n */\nexport function getDnsVerificationInstructions(attestationId: string, domain: string): string {\n const cleanDomain = domain.replace(/^https?:\\/\\//, '').replace(/\\/$/, '');\n\n return `To verify your DNS domain (${cleanDomain}):\n\n**Method 1: .well-known file (Recommended)**\n\n1. Create a file at: https://${cleanDomain}/.well-known/orangecheck.txt\n2. Add this content to the file:\n\n OrangeCheck Attestation Verification\n Domain: ${cleanDomain}\n Attestation ID: ${attestationId}\n Timestamp: ${new Date().toISOString()}\n\n3. Make sure the file is publicly accessible\n4. Return here and click \"Verify\"\n\n**Method 2: DNS TXT record (Advanced)**\n\n1. Add a TXT record to your DNS:\n Name: _orangecheck.${cleanDomain}\n Value: ${attestationId}\n\n2. Wait for DNS propagation (up to 24 hours)\n3. Return here and click \"Verify\"\n\nNote: Method 1 is faster and easier to set up.`;\n}\n\n/**\n * Generate well-known file content\n */\nexport function generateWellKnownContent(attestationId: string, domain: string): string {\n const cleanDomain = domain.replace(/^https?:\\/\\//, '').replace(/\\/$/, '');\n\n return `OrangeCheck Attestation Verification\n=====================================\n\nDomain: ${cleanDomain}\nAttestation ID: ${attestationId}\nTimestamp: ${new Date().toISOString()}\n\nThis file verifies that the owner of ${cleanDomain} controls the Bitcoin\naddress associated with attestation ${attestationId}.\n\nLearn more: https://ochk.io\n\n---\n\nVerification method: .well-known/orangecheck.txt\nProtocol: OrangeCheck Protocol (OCP)\n`;\n}\n\n/**\n * Get the expected well-known file URL\n */\nexport function getWellKnownUrl(domain: string): string {\n const cleanDomain = domain.replace(/^https?:\\/\\//, '').replace(/\\/$/, '');\n return `https://${cleanDomain}/.well-known/orangecheck.txt`;\n}\n\n/**\n * Validate domain format\n */\nexport function isValidDomain(domain: string): boolean {\n const cleanDomain = domain.replace(/^https?:\\/\\//, '').replace(/\\/$/, '');\n\n // Basic domain validation\n const domainPattern = /^[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,}$/i;\n return domainPattern.test(cleanDomain);\n}\n\n/**\n * Check if a DNS identity is already verified\n * This is a quick check using cached data\n */\nexport function isDnsIdentityVerified(\n attestationId: string,\n _domain: string,\n cachedContent?: string\n): boolean {\n if (!cachedContent) {\n return false;\n }\n\n return cachedContent.includes(attestationId);\n}\n","/**\n * GitHub Identity Verification\n * Verifies that a user controls a GitHub identity by checking for gists\n */\n\nimport { createLogger } from '../utils/logger';\n\nconst log = createLogger('identity-verification/github');\n\n/**\n * Gist file bodies are fetched from `raw_url`. GitHub serves these from\n * `gist.githubusercontent.com`, but `raw_url` is *server-controlled* — a\n * malicious response could point it anywhere (internal IPs, 169.254.169.254,\n * file://, etc.) and our verifier would happily fetch it. Allow-list the\n * expected host to close the SSRF door.\n */\nconst GITHUB_GIST_RAW_HOST = 'gist.githubusercontent.com';\n\n/** Cap fan-out so a user with 1k gists × many files can't pin the verifier. */\nconst MAX_GISTS_SCANNED = 30;\nconst MAX_FILES_PER_GIST = 10;\n\n/** Tight per-request deadline. Github is usually under 300ms. */\nconst FETCH_TIMEOUT_MS = 5_000;\n\n/** Reject usernames that don't match GitHub's published allowed character set\n * before we interpolate them into a URL. */\nconst GITHUB_USERNAME_RE = /^[a-zA-Z0-9](?:[a-zA-Z0-9]|-(?=[a-zA-Z0-9])){0,38}$/;\n\nfunction isSafeGistRawUrl(raw: string): boolean {\n try {\n const u = new URL(raw);\n return u.protocol === 'https:' && u.hostname === GITHUB_GIST_RAW_HOST;\n } catch {\n return false;\n }\n}\n\n/**\n * Verification result for GitHub identity\n */\nexport interface GitHubVerificationResult {\n verified: boolean;\n gistUrl?: string;\n gistId?: string;\n error?: string;\n}\n\n/**\n * GitHub gist file structure\n */\ninterface GitHubGistFile {\n filename: string;\n type: string;\n language: string | null;\n raw_url: string;\n size: number;\n content?: string;\n}\n\n/**\n * GitHub gist structure\n */\ninterface GitHubGist {\n id: string;\n html_url: string;\n description: string;\n public: boolean;\n created_at: string;\n updated_at: string;\n files: Record<string, GitHubGistFile>;\n}\n\n/**\n * Verify that a user controls a GitHub identity\n *\n * Verification method:\n * 1. Query GitHub API for user's public gists\n * 2. Check if any gist file contains the attestation ID\n * 3. Return the gist URL as proof\n *\n * @param attestationId - The attestation ID to look for\n * @param username - The GitHub username\n * @returns Verification result with gist URL if verified\n */\nexport async function verifyGitHubIdentity(\n attestationId: string,\n username: string\n): Promise<GitHubVerificationResult> {\n log.info({ attestationId, username }, 'Verifying GitHub identity');\n\n if (!GITHUB_USERNAME_RE.test(username)) {\n return { verified: false, error: 'invalid GitHub username format' };\n }\n\n try {\n // Query GitHub API for user's gists\n const response = await fetch(\n `https://api.github.com/users/${encodeURIComponent(username)}/gists?per_page=${MAX_GISTS_SCANNED}`,\n {\n headers: { Accept: 'application/vnd.github.v3+json' },\n signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),\n }\n );\n\n if (!response.ok) {\n if (response.status === 404) {\n return {\n verified: false,\n error: `GitHub user '${username}' not found`,\n };\n }\n\n return {\n verified: false,\n error: `GitHub API error: ${response.status} ${response.statusText}`,\n };\n }\n\n const gists = (await response.json()) as GitHubGist[];\n\n log.info({ username, gistCount: gists.length }, 'Fetched GitHub gists');\n\n // Check each gist for attestation ID (capped fan-out)\n for (const gist of gists.slice(0, MAX_GISTS_SCANNED)) {\n // Check gist description\n if (gist.description?.includes(attestationId)) {\n log.info({ username, gistId: gist.id }, 'Found attestation ID in gist description');\n return {\n verified: true,\n gistUrl: gist.html_url,\n gistId: gist.id,\n };\n }\n\n // Check each file in the gist (capped)\n const files = Object.entries(gist.files).slice(0, MAX_FILES_PER_GIST);\n for (const [filename, file] of files) {\n // If content is available, check it\n if (file.content && file.content.includes(attestationId)) {\n log.info(\n { username, gistId: gist.id, filename },\n 'Found attestation ID in gist file'\n );\n return {\n verified: true,\n gistUrl: gist.html_url,\n gistId: gist.id,\n };\n }\n\n // If content not available, fetch it from raw_url — but only\n // if raw_url is a real GitHub-hosted URL. Without the allow-list\n // we're an SSRF primitive for anyone controlling the API\n // response (including via a compromised GitHub mirror / MITM).\n if (!isSafeGistRawUrl(file.raw_url)) {\n log.warn(\n { filename, rawUrl: file.raw_url },\n 'skipping gist file: raw_url failed allow-list'\n );\n continue;\n }\n\n try {\n const fileResponse = await fetch(file.raw_url, {\n signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),\n });\n if (fileResponse.ok) {\n const content = await fileResponse.text();\n if (content.includes(attestationId)) {\n log.info(\n { username, gistId: gist.id, filename },\n 'Found attestation ID in gist file (fetched)'\n );\n return {\n verified: true,\n gistUrl: gist.html_url,\n gistId: gist.id,\n };\n }\n }\n } catch (err) {\n log.warn({ error: err, filename }, 'Failed to fetch gist file content');\n }\n }\n }\n\n log.info({ username }, 'No verification gist found');\n return {\n verified: false,\n error: 'No gist found containing the attestation ID',\n };\n } catch (err) {\n log.error({ error: err, username }, 'Failed to verify GitHub identity');\n return {\n verified: false,\n error: err instanceof Error ? err.message : 'Unknown error',\n };\n }\n}\n\n/**\n * Get verification instructions for GitHub identity\n */\nexport function getGitHubVerificationInstructions(attestationId: string): string {\n return `To verify your GitHub identity:\n\n1. Go to https://gist.github.com\n2. Create a new public gist\n3. Add a file with any name (e.g., \"orangecheck.txt\")\n4. Paste this text into the file:\n\n OrangeCheck Attestation Verification\n Attestation ID: ${attestationId}\n\n5. Click \"Create public gist\"\n6. Return here and click \"Verify\" to check\n\nThe verification will search your public gists for this attestation ID.`;\n}\n\n/**\n * Generate a gist template for verification\n */\nexport function generateGistTemplate(attestationId: string, username: string): string {\n return `# OrangeCheck Attestation Verification\n\nI am verifying my GitHub identity (@${username}) for OrangeCheck.\n\n**Attestation ID:** \\`${attestationId}\\`\n\n**Timestamp:** ${new Date().toISOString()}\n\nThis gist proves that I control both:\n- GitHub account: @${username}\n- Bitcoin address associated with attestation ${attestationId}\n\n---\n\nLearn more about OrangeCheck: https://ochk.io\n`;\n}\n\n/**\n * Check if a GitHub identity is already verified\n * This is a quick check using cached gist data\n */\nexport function isGitHubIdentityVerified(\n attestationId: string,\n _username: string,\n cachedGists: GitHubGist[]\n): boolean {\n return cachedGists.some((gist) => {\n // Check description\n if (gist.description?.includes(attestationId)) {\n return true;\n }\n\n // Check files\n return Object.values(gist.files).some((file) => file.content?.includes(attestationId));\n });\n}\n\n/**\n * Get direct link to create a verification gist. The args are accepted for\n * API parity with other identity providers but unused — GitHub doesn't accept\n * pre-fill params on the gist creation page.\n */\nexport function getGistCreationUrl(): string {\n return 'https://gist.github.com/';\n}\n","/**\n * Nostr Integration for OrangeCheck Protocol\n *\n * Implements NIP-78 (Application-specific data) for attestation publishing\n */\n\nimport type { AttestationEnvelope, IdentityBinding, NostrEvent } from './types';\n\nimport { createLogger } from './utils/logger';\n\nconst log = createLogger('ocp/nostr');\n\n/**\n * NIP-78 event kind for OrangeCheck attestations\n */\nexport const ATTESTATION_EVENT_KIND = 30078;\n\n/**\n * Default Nostr relays for OrangeCheck attestation publishing and lookup.\n *\n * Well-known relays with good uptime, plus the first-party family relay.\n * `relay.ochk.io` was missing here while attest.ochk.io's verifier has always\n * read from it, so the SDK and the reference implementation were looking in\n * different places — the SDK could miss a family attestation that the hosted\n * API found. It runs a kind allowlist (30078-30087, 30110-30114) and canonical OC d-tag\n * prefixes, and is always co-published alongside the public relays, never the\n * only copy. See https://github.com/orangecheck/oc-relay-infra.\n *\n * Order matters only for readability; queries fan out to all of them and\n * merge. Pass your own `relays` to any query to override this entirely — the\n * protocol does not require trusting any particular relay.\n */\nexport const DEFAULT_RELAYS: string[] = [\n 'wss://relay.damus.io',\n 'wss://nos.lol',\n 'wss://relay.snort.social',\n 'wss://relay.ochk.io',\n];\n\n/**\n * Wall-clock budget for a whole relay fan-out, shared by every relay in it.\n *\n * Queries below use Promise.allSettled, so a fan-out used to take as long as\n * its SLOWEST relay — and with a dead relay in DEFAULT_RELAYS that meant the\n * per-relay 10s timeout, every single call. Measured on 1.4.0:\n *\n * check({ addr }) with the old defaults 10.4s\n * check({ addr }) with the dead relay out 1.0s\n *\n * That is not merely slow, it broke a dependent outright: @orangecheck/gate\n * races check() against a 5s lookupTimeoutMs, so a 10.4s lookup lost the race\n * every time and no address-based gate decision could ever succeed.\n *\n * A shared deadline fixes the class: whichever relay is dead or slow next, a\n * fan-out costs at most this. Healthy relays answer well inside it (~1s for a\n * cold WebSocket plus a query). QUERY_TIMEOUT_MS remains the per-relay ceiling.\n *\n * The value is deliberately BELOW @orangecheck/gate's 5s default\n * lookupTimeoutMs, so a gate decision can still resolve in the worst case\n * rather than always losing its own race. Raising this above 5000 silently\n * breaks that dependent — change both together, or don't.\n */\nexport const FANOUT_DEADLINE_MS = 4000;\nconst QUERY_TIMEOUT_MS = 10000;\n\n/** Remaining budget for a relay, given the fan-out's shared deadline. */\nfunction budgetFor(deadlineAt: number): number {\n return Math.max(0, Math.min(QUERY_TIMEOUT_MS, deadlineAt - Date.now()));\n}\n\n/**\n * Create a NIP-78 event for an OrangeCheck attestation\n *\n * Kind: 30078 (Parameterized Replaceable Event)\n * d-tag: attestation_id (makes it replaceable by attestation ID)\n *\n * @param envelope - Attestation envelope\n * @param pubkey - Nostr public key (hex format)\n * @returns Unsigned Nostr event\n */\nexport function createAttestationEvent(\n envelope: AttestationEnvelope,\n pubkey: string\n): Omit<NostrEvent, 'id' | 'sig'> {\n // NIP-12 relays index SINGLE-LETTER tags only. A multi-letter tag like\n // `address` is stored verbatim and stays readable in the event, but it is\n // never indexed, so no `#address` filter can ever retrieve it. Without the\n // `t` tags below an attestation published by this SDK is invisible to\n // every by-address query on every relay — including attest.ochk.io's own\n // verifier, which queries `#t`. Keep both: `t` is what makes it findable,\n // `address` is human-readable diagnostics and is not load-bearing.\n const tags: string[][] = [\n ['d', envelope.attestation_id], // Parameterized replaceable event identifier (indexed)\n ['t', envelope.address], // Indexed btc-address tag — query with `#t`\n ['t', 'oc-attest'], // Family marker — `#t=[\"oc-attest\"]` for cross-cut discovery\n ['address', envelope.address], // Diagnostic / legacy — relay won't index this\n ['scheme', envelope.scheme], // Signature scheme (bip322 or legacy)\n ['issued_at', envelope.issued_at], // ISO timestamp\n ];\n\n // Add identity tags\n for (const identity of envelope.identities) {\n tags.push(['i', `${identity.protocol}:${identity.identifier}`]);\n }\n\n // Add expiration if present\n if (envelope.expires_at) {\n tags.push(['expires', envelope.expires_at]);\n }\n\n // Add relay hints if present\n if (envelope.relay_hints?.length) {\n for (const relay of envelope.relay_hints) {\n tags.push(['relay', relay]);\n }\n }\n\n // Content is the full attestation envelope as JSON\n const content = JSON.stringify(envelope, null, 2);\n\n return {\n kind: ATTESTATION_EVENT_KIND,\n tags,\n content,\n created_at: Math.floor(Date.parse(envelope.issued_at) / 1000),\n pubkey,\n };\n}\n\n/**\n * Parse identity bindings from Nostr event tags\n *\n * @param event - Nostr event\n * @returns Array of identity bindings\n */\nexport function parseIdentitiesFromEvent(event: NostrEvent): IdentityBinding[] {\n const identities: IdentityBinding[] = [];\n\n for (const tag of event.tags) {\n if (tag[0] === 'i' && tag[1]) {\n const colonIndex = tag[1].indexOf(':');\n if (colonIndex !== -1) {\n identities.push({\n protocol: tag[1].substring(0, colonIndex),\n identifier: tag[1].substring(colonIndex + 1),\n });\n }\n }\n }\n\n return identities;\n}\n\n/**\n * Extract attestation ID from Nostr event d-tag\n *\n * @param event - Nostr event\n * @returns Attestation ID or undefined if not found\n */\nexport function getAttestationIdFromEvent(event: NostrEvent): string | undefined {\n const dTag = event.tags.find((tag) => tag[0] === 'd');\n return dTag?.[1];\n}\n\n/**\n * Extract Bitcoin address from Nostr event tags\n *\n * @param event - Nostr event\n * @returns Bitcoin address or undefined if not found\n */\nexport function getAddressFromEvent(event: NostrEvent): string | undefined {\n const addressTag = event.tags.find((tag) => tag[0] === 'address');\n return addressTag?.[1];\n}\n\n/**\n * Publish a Nostr event to multiple relays\n * Returns array of relay URLs that successfully accepted the event\n */\nexport async function publishToRelays(\n event: NostrEvent,\n relays: string[] = DEFAULT_RELAYS\n): Promise<{ success: string[]; failed: string[] }> {\n const success: string[] = [];\n const failed: string[] = [];\n\n log.info({ eventId: event.id, relayCount: relays.length }, 'Publishing event to relays');\n\n // One shared deadline for the whole fan-out — see FANOUT_DEADLINE_MS.\n\n const deadlineAt = Date.now() + FANOUT_DEADLINE_MS;\n\n const publishPromises = relays.map(async (relayUrl) => {\n let ws: WebSocket | null = null;\n\n // One outcome per relay, first one wins. Without this a relay that\n // ACKs `OK true` and then emits an error or close event during\n // teardown lands in BOTH arrays, and the caller reports \"published\n // to 3 · rejected 3\" over the same three relays. The `failed.includes`\n // check this replaces only ever de-duplicated `failed` against\n // itself, so it could not catch a success/failure split.\n // attest.ochk.io's copy of this file has carried the guard for a\n // while; this is the back-port.\n let settled = false;\n const recordSuccess = (): void => {\n if (settled) return;\n settled = true;\n success.push(relayUrl);\n };\n const recordFailure = (): void => {\n if (settled) return;\n settled = true;\n failed.push(relayUrl);\n };\n\n try {\n // Connect to relay\n ws = new WebSocket(relayUrl);\n const wsRef = ws; // Capture for closures\n\n await new Promise<void>((resolve, reject) => {\n // One outer deadline covers connect + OK round-trip. The old\n // code cleared this on `onopen`, which meant a relay that\n // opened but never sent OK would hang the promise forever.\n const deadline = setTimeout(() => {\n wsRef.close();\n reject(new Error('Relay publish timeout'));\n }, budgetFor(deadlineAt));\n\n const cleanup = () => {\n clearTimeout(deadline);\n wsRef.close();\n };\n\n wsRef.onopen = () => {\n // Do NOT clear the deadline — we still need it in case\n // the relay never responds with OK.\n wsRef.send(JSON.stringify(['EVENT', event]));\n };\n\n wsRef.onmessage = (msg) => {\n try {\n const data = JSON.parse(msg.data);\n // Check for OK response\n if (data[0] === 'OK' && data[1] === event.id) {\n if (data[2] === true) {\n recordSuccess();\n cleanup();\n resolve();\n } else {\n log.warn(\n { relay: relayUrl, reason: data[3] },\n 'Relay rejected event'\n );\n recordFailure();\n cleanup();\n reject(new Error(data[3] || 'Relay rejected event'));\n }\n }\n } catch (err) {\n log.error(\n { relay: relayUrl, error: err },\n 'Failed to parse relay response'\n );\n recordFailure();\n cleanup();\n reject(err);\n }\n };\n\n wsRef.onerror = (err) => {\n log.error({ relay: relayUrl, error: err }, 'WebSocket error');\n recordFailure();\n cleanup();\n reject(err);\n };\n\n wsRef.onclose = () => {\n // Socket closed before we resolved/rejected — counts as\n // failure. clearTimeout here is safe (cleanup handles both).\n clearTimeout(deadline);\n };\n });\n } catch (err) {\n log.error({ relay: relayUrl, error: err }, 'Failed to publish to relay');\n recordFailure();\n } finally {\n // Ensure WebSocket is closed\n if (ws && ws.readyState !== WebSocket.CLOSED) {\n ws.close();\n }\n }\n });\n\n await Promise.allSettled(publishPromises);\n\n log.info({ successCount: success.length, failedCount: failed.length }, 'Publishing complete');\n\n return { success, failed };\n}\n\n/**\n * Query relays for attestations by attestation ID\n */\nexport async function queryByAttestationId(\n attestationId: string,\n relays: string[] = DEFAULT_RELAYS\n): Promise<NostrEvent[]> {\n const events: NostrEvent[] = [];\n\n log.info({ attestationId, relayCount: relays.length }, 'Querying relays for attestation');\n\n // One shared deadline for the whole fan-out — see FANOUT_DEADLINE_MS.\n\n const deadlineAt = Date.now() + FANOUT_DEADLINE_MS;\n\n const queryPromises = relays.map(async (relayUrl) => {\n let ws: WebSocket | null = null;\n\n try {\n ws = new WebSocket(relayUrl);\n const wsRef = ws; // Capture for closures\n\n await new Promise<void>((resolve, reject) => {\n const timeout = setTimeout(() => {\n wsRef.close();\n reject(new Error('Query timeout'));\n }, budgetFor(deadlineAt));\n\n const cleanup = () => {\n clearTimeout(timeout);\n wsRef.close();\n };\n\n wsRef.onopen = () => {\n // Send REQ message for kind 30078 with d-tag = attestation_id\n const subscriptionId = `ochk_${Date.now()}`;\n wsRef.send(\n JSON.stringify([\n 'REQ',\n subscriptionId,\n {\n kinds: [30078],\n '#d': [attestationId],\n limit: 1,\n },\n ])\n );\n };\n\n wsRef.onmessage = (msg) => {\n try {\n const data = JSON.parse(msg.data);\n if (data[0] === 'EVENT') {\n events.push(data[2]);\n } else if (data[0] === 'EOSE') {\n cleanup();\n resolve();\n }\n } catch (err) {\n log.error({ relay: relayUrl, error: err }, 'Failed to parse event');\n }\n };\n\n wsRef.onerror = (err) => {\n log.error({ relay: relayUrl, error: err }, 'WebSocket error during query');\n cleanup();\n reject(err);\n };\n\n wsRef.onclose = () => {\n clearTimeout(timeout);\n };\n });\n } catch (err) {\n log.error({ relay: relayUrl, error: err }, 'Failed to query relay');\n } finally {\n // Ensure WebSocket is closed\n if (ws && ws.readyState !== WebSocket.CLOSED) {\n ws.close();\n }\n }\n });\n\n await Promise.allSettled(queryPromises);\n\n log.info({ eventCount: events.length }, 'Query complete');\n\n return events;\n}\n\n/**\n * Query relays for attestations by Bitcoin address\n */\nexport async function queryByAddress(\n address: string,\n relays: string[] = DEFAULT_RELAYS\n): Promise<NostrEvent[]> {\n const events: NostrEvent[] = [];\n\n log.info({ address, relayCount: relays.length }, 'Querying relays for address');\n\n // One shared deadline for the whole fan-out — see FANOUT_DEADLINE_MS.\n\n const deadlineAt = Date.now() + FANOUT_DEADLINE_MS;\n\n const queryPromises = relays.map(async (relayUrl) => {\n try {\n const ws = new WebSocket(relayUrl);\n\n await new Promise<void>((resolve, reject) => {\n const timeout = setTimeout(() => {\n ws.close();\n reject(new Error('Query timeout'));\n }, budgetFor(deadlineAt));\n\n ws.onopen = () => {\n const subscriptionId = `ochk_addr_${Date.now()}`;\n ws.send(\n JSON.stringify([\n 'REQ',\n subscriptionId,\n {\n kinds: [30078],\n // `#t`, NOT `#address`: NIP-12 relays index\n // single-letter tags only, so `#address`\n // matched nothing on any relay and this\n // function returned empty for every real\n // attestation. attest.ochk.io's verifier has\n // queried `#t` since the same fix landed\n // there; this is the back-port.\n '#t': [address],\n limit: 50,\n },\n ])\n );\n };\n\n ws.onmessage = (msg) => {\n try {\n const data = JSON.parse(msg.data);\n if (data[0] === 'EVENT') {\n events.push(data[2]);\n } else if (data[0] === 'EOSE') {\n clearTimeout(timeout);\n ws.close();\n resolve();\n }\n } catch (err) {\n log.error({ relay: relayUrl, error: err }, 'Failed to parse event');\n }\n };\n\n ws.onerror = (err) => {\n clearTimeout(timeout);\n log.error({ relay: relayUrl, error: err }, 'WebSocket error during query');\n reject(err);\n };\n });\n } catch (err) {\n log.error({ relay: relayUrl, error: err }, 'Failed to query relay');\n }\n });\n\n await Promise.allSettled(queryPromises);\n\n log.info({ eventCount: events.length }, 'Query complete');\n\n return events;\n}\n\n/**\n * Query relays for attestations by identity (any protocol)\n */\nexport async function queryByIdentity(\n protocol: string,\n identifier: string,\n relays: string[] = DEFAULT_RELAYS\n): Promise<NostrEvent[]> {\n const events: NostrEvent[] = [];\n const identityTag = `${protocol}:${identifier}`;\n\n log.info({ protocol, identifier, relayCount: relays.length }, 'Querying relays for identity');\n\n // One shared deadline for the whole fan-out — see FANOUT_DEADLINE_MS.\n\n const deadlineAt = Date.now() + FANOUT_DEADLINE_MS;\n\n const queryPromises = relays.map(async (relayUrl) => {\n try {\n const ws = new WebSocket(relayUrl);\n\n await new Promise<void>((resolve, reject) => {\n const timeout = setTimeout(() => {\n ws.close();\n reject(new Error('Query timeout'));\n }, budgetFor(deadlineAt));\n\n ws.onopen = () => {\n const subscriptionId = `ochk_identity_${Date.now()}`;\n ws.send(\n JSON.stringify([\n 'REQ',\n subscriptionId,\n {\n kinds: [30078],\n '#i': [identityTag],\n limit: 10,\n },\n ])\n );\n };\n\n ws.onmessage = (msg) => {\n try {\n const data = JSON.parse(msg.data);\n if (data[0] === 'EVENT') {\n events.push(data[2]);\n } else if (data[0] === 'EOSE') {\n clearTimeout(timeout);\n ws.close();\n resolve();\n }\n } catch (err) {\n log.error({ relay: relayUrl, error: err }, 'Failed to parse event');\n }\n };\n\n ws.onerror = (err) => {\n clearTimeout(timeout);\n log.error({ relay: relayUrl, error: err }, 'WebSocket error during query');\n reject(err);\n };\n });\n } catch (err) {\n log.error({ relay: relayUrl, error: err }, 'Failed to query relay');\n }\n });\n\n await Promise.allSettled(queryPromises);\n\n log.info({ eventCount: events.length }, 'Query complete');\n\n return events;\n}\n\n/**\n * Verify attestation envelope integrity\n * Checks that the attestation ID matches the message hash\n */\nexport async function verifyAttestationEnvelope(envelope: AttestationEnvelope): Promise<boolean> {\n try {\n // Re-compute attestation ID from message\n const encoder = new TextEncoder();\n const data = encoder.encode(envelope.message);\n const hashBuffer = await crypto.subtle.digest('SHA-256', data);\n const hashArray = Array.from(new Uint8Array(hashBuffer));\n const computedId = hashArray.map((b) => b.toString(16).padStart(2, '0')).join('');\n\n return computedId === envelope.attestation_id;\n } catch (err) {\n log.error({ error: err }, 'Failed to verify attestation envelope');\n return false;\n }\n}\n\n/**\n * Parse attestation envelope from Nostr event content\n */\nexport function parseAttestationFromEvent(event: NostrEvent): AttestationEnvelope | null {\n try {\n const envelope = JSON.parse(event.content) as AttestationEnvelope;\n return envelope;\n } catch (err) {\n log.error({ eventId: event.id, error: err }, 'Failed to parse attestation from event');\n return null;\n }\n}\n","/**\n * Nostr Identity Verification\n * Verifies that a user controls a Nostr identity by checking for proof events\n *\n * Two previous bugs in this file made verification a lie:\n * 1. `npubToHex` returned the bech32 string unchanged when given an npub\n * (only the already-hex branch worked). Downstream authors filters used\n * \"npub1…\" as a literal Nostr pubkey, which no relay indexes — so the\n * search *always* returned zero events and fell through to \"no proof\".\n * Benign false-negative, except combined with (2).\n * 2. `verifyNostrEventSignature` returned `true` for any event with a\n * 128-char-hex-looking `sig`. No actual schnorr check. An attacker\n * could publish a note from any pubkey, drop someone else's attestation\n * ID into its content, and the verifier would accept.\n *\n * Fixed by decoding npubs with @scure/base's bech32 and verifying the event\n * signature with @noble/curves' schnorr over sha256(id-preimage).\n */\n\nimport type { NostrEvent } from '../types';\n\nimport { schnorr } from '@noble/curves/secp256k1.js';\nimport { sha256 } from '@noble/hashes/sha2.js';\nimport { bech32 } from '@scure/base';\n\nimport { DEFAULT_RELAYS } from '../nostr';\nimport { createLogger } from '../utils/logger';\n\nconst log = createLogger('identity-verification/nostr');\n\nconst HEX_64_RE = /^[0-9a-f]{64}$/;\n\nfunction toHex(bytes: Uint8Array): string {\n let out = '';\n for (const b of bytes) out += b.toString(16).padStart(2, '0');\n return out;\n}\n\nfunction fromHex(hex: string): Uint8Array {\n if (hex.length % 2 !== 0) throw new Error('odd-length hex');\n const out = new Uint8Array(hex.length / 2);\n for (let i = 0; i < out.length; i++) {\n out[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);\n }\n return out;\n}\n\n/**\n * Verification result for Nostr identity\n */\nexport interface NostrVerificationResult {\n verified: boolean;\n event?: NostrEvent;\n error?: string;\n}\n\n/**\n * Decode a Nostr public key in `npub1…` bech32 form (or pass through if\n * already 64-char hex). Throws on invalid input — callers should treat a\n * throw as \"not a valid npub\" and fail closed.\n */\nfunction npubToHex(npub: string): string {\n const trimmed = npub.trim();\n if (HEX_64_RE.test(trimmed.toLowerCase())) {\n return trimmed.toLowerCase();\n }\n if (!trimmed.toLowerCase().startsWith('npub1')) {\n throw new Error(`not an npub: ${trimmed.slice(0, 12)}…`);\n }\n // `@scure/base` bech32 decoder; unlimited length is fine for NIP-19.\n const decoded = bech32.decode(trimmed.toLowerCase() as `${string}1${string}`, 1023);\n if (decoded.prefix !== 'npub') {\n throw new Error(`bech32 prefix mismatch: expected npub, got ${decoded.prefix}`);\n }\n const bytes = bech32.fromWords(decoded.words);\n if (bytes.length !== 32) {\n throw new Error(`invalid npub payload length: ${bytes.length}`);\n }\n return toHex(Uint8Array.from(bytes));\n}\n\n/**\n * Query Nostr relays for events from a specific author\n */\nasync function queryNostrEvents(options: {\n authors: string[];\n kinds: number[];\n search?: string;\n relays?: string[];\n limit?: number;\n}): Promise<NostrEvent[]> {\n const { authors, kinds, search, relays = DEFAULT_RELAYS, limit = 100 } = options;\n const events: NostrEvent[] = [];\n\n log.info({ authors, kinds, relayCount: relays.length }, 'Querying Nostr for events');\n\n const queryPromises = relays.map(async (relayUrl) => {\n let ws: WebSocket | null = null;\n\n try {\n ws = new WebSocket(relayUrl);\n const wsRef = ws;\n\n await new Promise<void>((resolve, reject) => {\n const timeout = setTimeout(() => {\n wsRef.close();\n reject(new Error('Query timeout'));\n }, 10000);\n\n const cleanup = () => {\n clearTimeout(timeout);\n wsRef.close();\n };\n\n wsRef.onopen = () => {\n const subscriptionId = `verify_${Date.now()}`;\n const filter: Record<string, unknown> = {\n authors,\n kinds,\n limit,\n };\n\n wsRef.send(JSON.stringify(['REQ', subscriptionId, filter]));\n };\n\n wsRef.onmessage = (msg) => {\n try {\n const data = JSON.parse(msg.data);\n if (data[0] === 'EVENT') {\n const event = data[2] as NostrEvent;\n\n // If search term provided, filter by content\n if (search) {\n if (event.content.includes(search)) {\n events.push(event);\n }\n } else {\n events.push(event);\n }\n } else if (data[0] === 'EOSE') {\n cleanup();\n resolve();\n }\n } catch (err) {\n log.error({ relay: relayUrl, error: err }, 'Failed to parse event');\n }\n };\n\n wsRef.onerror = (err) => {\n log.error({ relay: relayUrl, error: err }, 'WebSocket error during query');\n cleanup();\n reject(err);\n };\n\n wsRef.onclose = () => {\n clearTimeout(timeout);\n };\n });\n } catch (err) {\n log.error({ relay: relayUrl, error: err }, 'Failed to query relay');\n } finally {\n if (ws && ws.readyState !== WebSocket.CLOSED) {\n ws.close();\n }\n }\n });\n\n await Promise.allSettled(queryPromises);\n\n log.info({ eventCount: events.length }, 'Query complete');\n\n return events;\n}\n\n/**\n * Verify a Nostr event's schnorr signature per NIP-01:\n *\n * id = sha256(JSON.stringify([0, pubkey, created_at, kind, tags, content]))\n * sig = schnorr_sign(id, secret_key)\n * check = schnorr_verify(id, pubkey, sig)\n *\n * Fails closed on any shape problem, decoding error, or mismatch. A true\n * return from this function is the ONLY thing that should be treated as\n * proof the event really came from the claimed pubkey.\n */\nasync function verifyNostrEventSignature(event: NostrEvent): Promise<boolean> {\n try {\n if (\n !event.id ||\n !event.sig ||\n !event.pubkey ||\n typeof event.id !== 'string' ||\n typeof event.sig !== 'string' ||\n typeof event.pubkey !== 'string' ||\n event.sig.length !== 128 ||\n !HEX_64_RE.test(event.id) ||\n !HEX_64_RE.test(event.pubkey)\n ) {\n return false;\n }\n\n // Recompute the event id from the canonical NIP-01 preimage. If the\n // relay (or an attacker) altered any field since signing, id won't\n // match and we reject.\n const preimage = JSON.stringify([\n 0,\n event.pubkey,\n event.created_at,\n event.kind,\n event.tags ?? [],\n event.content ?? '',\n ]);\n const computedId = toHex(sha256(new TextEncoder().encode(preimage)));\n if (computedId !== event.id.toLowerCase()) {\n log.warn(\n { eventId: event.id, computedId },\n 'nostr event id does not match its content'\n );\n return false;\n }\n\n return schnorr.verify(fromHex(event.sig), fromHex(event.id), fromHex(event.pubkey));\n } catch (err) {\n log.warn({ err }, 'schnorr verify threw');\n return false;\n }\n}\n\n/**\n * Verify that a user controls a Nostr identity\n *\n * Verification method:\n * 1. Query Nostr relays for kind 1 (text note) events from the claimed npub\n * 2. Check if any event content contains the attestation ID\n * 3. Verify the event signature\n *\n * @param attestationId - The attestation ID to look for\n * @param npub - The Nostr public key (npub format or hex)\n * @param relays - Optional list of relays to query\n * @returns Verification result with proof event if verified\n */\nexport async function verifyNostrIdentity(\n attestationId: string,\n npub: string,\n relays?: string[]\n): Promise<NostrVerificationResult> {\n log.info({ attestationId, npub }, 'Verifying Nostr identity');\n\n try {\n // Convert npub to hex if needed\n const pubkeyHex = npubToHex(npub);\n\n // Query Nostr for kind 1 events from this pubkey containing attestation ID\n const events = await queryNostrEvents({\n authors: [pubkeyHex],\n kinds: [1], // Text notes\n search: attestationId,\n relays,\n });\n\n if (events.length === 0) {\n log.info({ npub }, 'No verification events found');\n return {\n verified: false,\n error: 'No verification event found. User must post a note containing the attestation ID.',\n };\n }\n\n // Check each event for attestation ID and verify signature\n for (const event of events) {\n if (event.content.includes(attestationId)) {\n // Verify event signature\n const isValid = await verifyNostrEventSignature(event);\n\n if (isValid) {\n log.info({ npub, eventId: event.id }, 'Nostr identity verified');\n return {\n verified: true,\n event,\n };\n } else {\n log.warn({ npub, eventId: event.id }, 'Invalid event signature');\n }\n }\n }\n\n return {\n verified: false,\n error: 'Found events but none had valid signatures',\n };\n } catch (err) {\n log.error({ error: err, npub }, 'Failed to verify Nostr identity');\n return {\n verified: false,\n error: err instanceof Error ? err.message : 'Unknown error',\n };\n }\n}\n\n/**\n * Get verification instructions for Nostr identity\n */\nexport function getNostrVerificationInstructions(attestationId: string): string {\n return `To verify your Nostr identity:\n\n1. Open your Nostr client (Damus, Amethyst, Primal, etc.)\n2. Post a note containing this text:\n\n Verifying my OrangeCheck attestation: ${attestationId}\n\n3. Wait a few seconds for the note to propagate to relays\n4. Return here and click \"Verify\" to check\n\nThe verification will search Nostr relays for your note and confirm you control this identity.`;\n}\n\n/**\n * Check if a Nostr identity is already verified\n * This is a quick check that doesn't re-query relays\n */\nexport function isNostrIdentityVerified(\n attestationId: string,\n npub: string,\n cachedEvents: NostrEvent[]\n): boolean {\n const pubkeyHex = npubToHex(npub);\n\n return cachedEvents.some(\n (event) =>\n event.pubkey === pubkeyHex && event.kind === 1 && event.content.includes(attestationId)\n );\n}\n","/**\n * Twitter Identity Verification\n * Verifies that a user controls a Twitter identity\n *\n * Note: Twitter API requires authentication and has rate limits.\n * This implementation uses manual verification where the user provides a tweet URL.\n */\n\nimport { createLogger } from '../utils/logger';\n\nconst log = createLogger('identity-verification/twitter');\n\n/**\n * Verification result for Twitter identity\n */\nexport interface TwitterVerificationResult {\n verified: boolean;\n tweetUrl?: string;\n tweetId?: string;\n error?: string;\n}\n\n/**\n * Verify Twitter identity via manual tweet URL\n *\n * @param attestationId - The attestation ID to look for\n * @param handle - The Twitter handle (with or without @)\n * @param tweetUrl - Optional tweet URL provided by user\n * @returns Verification result\n */\nexport async function verifyTwitterIdentity(\n attestationId: string,\n handle: string,\n tweetUrl?: string\n): Promise<TwitterVerificationResult> {\n log.info({ attestationId, handle, tweetUrl }, 'Verifying Twitter identity');\n\n if (!tweetUrl) {\n return {\n verified: false,\n error: 'Tweet URL required for verification',\n };\n }\n\n try {\n // Extract tweet ID from URL\n const tweetId = extractTweetId(tweetUrl);\n\n if (!tweetId) {\n return {\n verified: false,\n error: 'Invalid tweet URL format',\n };\n }\n\n // For now, we can't verify without Twitter API access\n // Return instructions for manual verification\n log.info({ handle, tweetId }, 'Manual verification required');\n\n return {\n verified: false,\n tweetUrl,\n tweetId,\n error: 'Automatic verification not available. Please verify manually by checking the tweet.',\n };\n } catch (err) {\n log.error({ error: err, handle }, 'Failed to verify Twitter identity');\n return {\n verified: false,\n error: err instanceof Error ? err.message : 'Unknown error',\n };\n }\n}\n\n/**\n * Extract tweet ID from various Twitter URL formats\n */\nfunction extractTweetId(url: string): string | null {\n // Support various Twitter URL formats:\n // https://twitter.com/username/status/1234567890\n // https://x.com/username/status/1234567890\n // https://mobile.twitter.com/username/status/1234567890\n\n const patterns = [\n /(?:twitter\\.com|x\\.com)\\/\\w+\\/status\\/(\\d+)/,\n /(?:mobile\\.twitter\\.com)\\/\\w+\\/status\\/(\\d+)/,\n ];\n\n for (const pattern of patterns) {\n const match = url.match(pattern);\n if (match && match[1]) {\n return match[1];\n }\n }\n\n return null;\n}\n\n/**\n * Get verification instructions for Twitter identity\n */\nexport function getTwitterVerificationInstructions(attestationId: string, handle: string): string {\n const cleanHandle = handle.startsWith('@') ? handle.slice(1) : handle;\n\n return `To verify your Twitter identity (@${cleanHandle}):\n\n1. Go to https://twitter.com/compose/tweet\n2. Post a tweet with this text:\n\n Verifying my OrangeCheck attestation: ${attestationId}\n\n3. After posting, copy the tweet URL\n4. Paste the URL below and click \"Verify\"\n\nExample tweet URL:\nhttps://twitter.com/${cleanHandle}/status/1234567890\n\nThe verification will check that the tweet exists and contains the attestation ID.`;\n}\n\n/**\n * Generate tweet text for verification\n */\nexport function generateTweetText(attestationId: string): string {\n return `Verifying my OrangeCheck attestation: ${attestationId}\n\nProving Bitcoin reputation with cryptographic signatures.\n\n#OrangeCheck #Bitcoin`;\n}\n\n/**\n * Get direct link to compose a verification tweet\n */\nexport function getTwitterComposeUrl(attestationId: string): string {\n const text = generateTweetText(attestationId);\n const encoded = encodeURIComponent(text);\n\n return `https://twitter.com/intent/tweet?text=${encoded}`;\n}\n\n/**\n * Validate tweet URL format\n */\nexport function isValidTweetUrl(url: string): boolean {\n return extractTweetId(url) !== null;\n}\n\n/**\n * Get tweet URL from handle and tweet ID\n */\nexport function getTweetUrl(handle: string, tweetId: string): string {\n const cleanHandle = handle.startsWith('@') ? handle.slice(1) : handle;\n return `https://twitter.com/${cleanHandle}/status/${tweetId}`;\n}\n\n/**\n * Manual verification helper\n * Returns instructions for verifier to manually check the tweet\n */\nexport function getManualVerificationInstructions(\n attestationId: string,\n handle: string,\n tweetUrl: string\n): string {\n return `Manual Verification Steps:\n\n1. Open this tweet: ${tweetUrl}\n2. Verify it's posted by @${handle}\n3. Check that the tweet contains: ${attestationId}\n4. Confirm the tweet is public and not deleted\n\nIf all checks pass, the identity is verified.`;\n}\n","import type { IdentityBinding } from '../types';\nimport type { DnsVerificationResult } from './dns';\nimport type { GitHubVerificationResult } from './github';\nimport type { NostrVerificationResult } from './nostr';\nimport type { TwitterVerificationResult } from './twitter';\n\nimport { logVerificationError } from '../utils/logger';\nimport { enforceRateLimit, identityVerificationLimiter } from '../utils/rate-limiter';\nimport { getDnsVerificationInstructions, verifyDnsIdentity } from './dns';\nimport { getGitHubVerificationInstructions, verifyGitHubIdentity } from './github';\nimport { getNostrVerificationInstructions, verifyNostrIdentity } from './nostr';\nimport { getTwitterVerificationInstructions, verifyTwitterIdentity } from './twitter';\n\n/**\n * Identity Verification\n *\n * Unified API for verifying identity bindings across multiple protocols\n */\n\nexport * from './nostr';\nexport * from './github';\nexport * from './twitter';\nexport * from './dns';\n\n/**\n * Unified verification result across all protocols\n */\nexport type VerificationResult =\n | NostrVerificationResult\n | GitHubVerificationResult\n | TwitterVerificationResult\n | DnsVerificationResult;\n\n/**\n * Options for identity verification\n */\nexport interface VerifyIdentityOptions {\n /** Tweet URL for Twitter verification */\n tweetUrl?: string;\n /** Relay URLs for Nostr verification */\n relays?: string[];\n}\n\n/**\n * Verify an identity binding\n *\n * @param attestationId - The attestation ID to verify against\n * @param identity - The identity binding to verify\n * @param options - Protocol-specific verification options\n * @returns Verification result with verified status and optional error\n *\n * @example\n * ```typescript\n * const result = await verifyIdentity(\n * 'abc123...',\n * { protocol: 'nostr', identifier: 'npub1...' },\n * { relays: ['wss://relay.damus.io'] }\n * );\n *\n * if (result.verified) {\n * console.log('Identity verified!');\n * }\n * ```\n */\nexport async function verifyIdentity(\n attestationId: string,\n identity: IdentityBinding,\n options?: VerifyIdentityOptions\n): Promise<VerificationResult> {\n const { protocol, identifier } = identity;\n\n // Rate limiting: 5 requests per minute per identity\n const rateLimitKey = `${protocol}:${identifier}`;\n try {\n enforceRateLimit(identityVerificationLimiter, rateLimitKey);\n } catch (err) {\n return {\n verified: false,\n error: err instanceof Error ? err.message : 'Rate limit exceeded',\n };\n }\n\n try {\n switch (protocol) {\n case 'nostr':\n return await verifyNostrIdentity(attestationId, identifier, options?.relays);\n\n case 'github':\n return await verifyGitHubIdentity(attestationId, identifier);\n\n case 'twitter':\n return await verifyTwitterIdentity(attestationId, identifier, options?.tweetUrl);\n\n case 'dns':\n return await verifyDnsIdentity(attestationId, identifier);\n\n case 'email':\n // Email verification not implemented yet\n return {\n verified: false,\n error: 'Email verification not implemented',\n };\n\n case 'web':\n // Web verification similar to DNS\n return await verifyDnsIdentity(attestationId, identifier);\n\n case 'did':\n // DID verification not implemented yet\n return {\n verified: false,\n error: 'DID verification not implemented',\n };\n\n default:\n return {\n verified: false,\n error: `Unknown protocol: ${protocol}`,\n };\n }\n } catch (error) {\n // Log error\n logVerificationError(\n protocol,\n identifier,\n error instanceof Error ? error : new Error(String(error)),\n {\n attestationId,\n }\n );\n\n return {\n verified: false,\n error: error instanceof Error ? error.message : 'Verification failed',\n };\n }\n}\n\n/**\n * Get verification instructions for an identity\n */\nexport function getVerificationInstructions(\n attestationId: string,\n identity: IdentityBinding\n): string {\n const { protocol, identifier } = identity;\n\n switch (protocol) {\n case 'nostr':\n return getNostrVerificationInstructions(attestationId);\n\n case 'github':\n return getGitHubVerificationInstructions(attestationId);\n\n case 'twitter':\n return getTwitterVerificationInstructions(attestationId, identifier);\n\n case 'dns':\n return getDnsVerificationInstructions(attestationId, identifier);\n\n case 'email':\n return `Email verification not implemented yet.`;\n\n case 'web':\n return getDnsVerificationInstructions(attestationId, identifier);\n\n case 'did':\n return `DID verification not implemented yet.`;\n\n default:\n return `Unknown protocol: ${protocol}`;\n }\n}\n\n/**\n * Check if a protocol supports automatic verification\n */\nexport function supportsAutomaticVerification(protocol: string): boolean {\n return ['nostr', 'github', 'dns', 'web'].includes(protocol);\n}\n\n/**\n * Check if a protocol requires manual verification\n */\nexport function requiresManualVerification(protocol: string): boolean {\n return ['twitter', 'email'].includes(protocol);\n}\n\n/**\n * Get verification status for multiple identities\n */\nexport async function verifyAllIdentities(\n attestationId: string,\n identities: IdentityBinding[],\n options?: {\n tweetUrls?: Record<string, string>; // Map of identifier -> tweet URL\n relays?: string[];\n }\n): Promise<Map<string, VerificationResult>> {\n const results = new Map<string, VerificationResult>();\n\n await Promise.all(\n identities.map(async (identity) => {\n const key = `${identity.protocol}:${identity.identifier}`;\n const tweetUrl = options?.tweetUrls?.[identity.identifier];\n\n const result = await verifyIdentity(attestationId, identity, {\n tweetUrl,\n relays: options?.relays,\n });\n\n results.set(key, result);\n })\n );\n\n return results;\n}\n\n/**\n * Get summary of verification results\n */\nexport function getVerificationSummary(results: Map<string, VerificationResult>): {\n total: number;\n verified: number;\n failed: number;\n pending: number;\n} {\n let verified = 0;\n let failed = 0;\n let pending = 0;\n\n for (const result of results.values()) {\n if (result.verified) {\n verified++;\n } else if (result.error?.includes('not implemented')) {\n pending++;\n } else {\n failed++;\n }\n }\n\n return {\n total: results.size,\n verified,\n failed,\n pending,\n };\n}\n"]}
1
+ {"version":3,"sources":["../src/utils/logger.ts","../src/utils/rate-limiter.ts","../src/identity/dns.ts","../src/identity/github.ts","../src/nostr.ts","../src/identity/nostr.ts","../src/identity/twitter.ts","../src/identity/index.ts"],"names":["log","bech32","sha256","schnorr"],"mappings":";;;;;;;AAgDO,SAAS,aAAa,SAAA,EAA2B;AACpD,EAAA,MAAM,SAAA,GAAY,CAAC,KAAA,KAA8C;AAC7D,IAAyB,OAAO,KAAA;AAMJ,EAChC,CAAA;AAEA,EAAA,MAAM,gBAAgB,CAAC,GAAA,KAAwB,CAAA,CAAA,EAAI,SAAS,KAAK,GAAG,CAAA,CAAA;AAEpE,EAAA,OAAO;AAAA,IACH,KAAA,EAAO,CAAC,gBAAA,EAAuC,OAAA,KAA2B;AACtE,MAAA,IAAI,SAAA,CAAiB,CAAA,EAAG;AACpB,QAAA,IAAI,OAAO,qBAAqB,QAAA,EAAU;AACtC,UAAA,OAAA,CAAQ,KAAA,CAAM,aAAA,CAAc,gBAAgB,CAAC,CAAA;AAAA,QACjD,CAAA,MAAO;AACH,UAAA,OAAA,CAAQ,KAAA,CAAM,aAAA,CAAc,OAAA,IAAW,OAAO,GAAG,gBAAgB,CAAA;AAAA,QACrE;AAAA,MACJ;AAAA,IACJ,CAAA;AAAA,IACA,IAAA,EAAM,CAAC,gBAAA,EAAuC,OAAA,KAA2B;AACrE,MAAA,IAAI,SAAA,CAAgB,CAAA,EAAG;AACnB,QAAA,IAAI,OAAO,qBAAqB,QAAA,EAAU;AACtC,UAAA,OAAA,CAAQ,IAAA,CAAK,aAAA,CAAc,gBAAgB,CAAC,CAAA;AAAA,QAChD,CAAA,MAAO;AACH,UAAA,OAAA,CAAQ,IAAA,CAAK,aAAA,CAAc,OAAA,IAAW,MAAM,GAAG,gBAAgB,CAAA;AAAA,QACnE;AAAA,MACJ;AAAA,IACJ,CAAA;AAAA,IACA,IAAA,EAAM,CAAC,gBAAA,EAAuC,OAAA,KAA2B;AACrE,MAAA,IAAI,SAAA,CAAgB,CAAA,EAAG;AACnB,QAAA,IAAI,OAAO,qBAAqB,QAAA,EAAU;AACtC,UAAA,OAAA,CAAQ,IAAA,CAAK,aAAA,CAAc,gBAAgB,CAAC,CAAA;AAAA,QAChD,CAAA,MAAO;AACH,UAAA,OAAA,CAAQ,IAAA,CAAK,aAAA,CAAc,OAAA,IAAW,SAAS,GAAG,gBAAgB,CAAA;AAAA,QACtE;AAAA,MACJ;AAAA,IACJ,CAAA;AAAA,IACA,KAAA,EAAO,CAAC,gBAAA,EAAuC,OAAA,KAA2B;AACtE,MAAA,IAAI,SAAA,CAAiB,CAAA,EAAG;AACpB,QAAA,IAAI,OAAO,qBAAqB,QAAA,EAAU;AACtC,UAAA,OAAA,CAAQ,KAAA,CAAM,aAAA,CAAc,gBAAgB,CAAC,CAAA;AAAA,QACjD,CAAA,MAAO;AACH,UAAA,OAAA,CAAQ,KAAA,CAAM,aAAA,CAAc,OAAA,IAAW,OAAO,GAAG,gBAAgB,CAAA;AAAA,QACrE;AAAA,MACJ;AAAA,IACJ;AAAA,GACJ;AACJ;AASO,SAAS,oBAAA,CACZ,QAAA,EACA,UAAA,EACA,KAAA,EACA,OAAA,EACI;AACJ,EAAA,MAAM,MAAA,GAAS,aAAa,uBAAuB,CAAA;AACnD,EAAA,MAAA,CAAO,KAAA;AAAA,IACH;AAAA,MACI,QAAA;AAAA,MACA,UAAA;AAAA,MACA,OAAO,KAAA,CAAM,OAAA;AAAA,MACb,OAAO,KAAA,CAAM,KAAA;AAAA,MACb,GAAG;AAAA,KACP;AAAA,IACA;AAAA,GACJ;AACJ;;;AC1GO,IAAM,cAAA,GAAN,cAA6B,KAAA,CAAM;AAAA,EACtC,WAAA,CAAY,UAAU,8CAAA,EAAgD;AAClE,IAAA,KAAA,CAAM,OAAO,CAAA;AACb,IAAA,IAAA,CAAK,IAAA,GAAO,gBAAA;AAAA,EAChB;AACJ,CAAA;AAQO,SAAS,iBAAA,CAAkB,aAAqB,QAAA,EAA+B;AAQlF,EAAA,OAAO;AAAA,IACH,WAAA;AAAA,IACA,QAAA;AAAA,IACA,QAAA,sBAAc,GAAA;AAAsB,GACxC;AACJ;AAQO,SAAS,cAAA,CAAe,SAAsB,GAAA,EAAsB;AACvE,EAAA,MAAM,GAAA,GAAM,KAAK,GAAA,EAAI;AACrB,EAAA,MAAM,WAAW,OAAA,CAAQ,QAAA,CAAS,GAAA,CAAI,GAAG,KAAK,EAAC;AAG/C,EAAA,MAAM,aAAA,GAAgB,SAAS,MAAA,CAAO,CAAC,cAAc,GAAA,GAAM,SAAA,GAAY,QAAQ,QAAQ,CAAA;AAGvF,EAAA,IAAI,aAAA,CAAc,MAAA,IAAU,OAAA,CAAQ,WAAA,EAAa;AAC7C,IAAA,OAAO,KAAA;AAAA,EACX;AAGA,EAAA,aAAA,CAAc,KAAK,GAAG,CAAA;AACtB,EAAA,OAAA,CAAQ,QAAA,CAAS,GAAA,CAAI,GAAA,EAAK,aAAa,CAAA;AAEvC,EAAA,OAAO,IAAA;AACX;AAQO,SAAS,gBAAA,CAAiB,SAAsB,GAAA,EAAmB;AACtE,EAAA,IAAI,CAAC,cAAA,CAAe,OAAA,EAAS,GAAG,CAAA,EAAG;AAC/B,IAAA,MAAM,IAAI,cAAA,EAAe;AAAA,EAC7B;AACJ;AA4BO,IAAM,2BAAA,GAA8B,iBAAA,CAAkB,CAAA,EAAG,GAAK,CAAA;;;ACvGrE,IAAM,GAAA,GAAM,aAAa,2BAA2B,CAAA;AASpD,IAAM,eAAA,GACF,oGAAA;AACJ,IAAM,eAAA,GACF,mJAAA;AAGJ,IAAM,qBAAA,GAAwB,GAAA;AAE9B,SAAS,iBAAiB,IAAA,EAAuB;AAC7C,EAAA,MAAM,CAAA,GAAI,KAAK,WAAA,EAAY;AAC3B,EAAA,IAAI,eAAA,CAAgB,IAAA,CAAK,CAAC,CAAA,EAAG,OAAO,KAAA;AACpC,EAAA,IAAI,eAAA,CAAgB,IAAA,CAAK,CAAC,CAAA,EAAG,OAAO,KAAA;AAGpC,EAAA,IAAI,CAAA,CAAE,WAAW,GAAG,CAAA,IAAK,eAAe,IAAA,CAAK,CAAC,GAAG,OAAO,KAAA;AACxD,EAAA,OAAO,IAAA;AACX;AAuBA,eAAsB,iBAAA,CAClB,eACA,MAAA,EAC8B;AAC9B,EAAA,GAAA,CAAI,IAAA,CAAK,EAAE,aAAA,EAAe,MAAA,IAAU,wBAAwB,CAAA;AAG5D,EAAA,MAAM,WAAA,GAAc,OAAO,OAAA,CAAQ,cAAA,EAAgB,EAAE,CAAA,CAAE,OAAA,CAAQ,OAAO,EAAE,CAAA;AAGxE,EAAA,IAAI,CAAC,aAAA,CAAc,WAAW,CAAA,EAAG;AAC7B,IAAA,OAAO,EAAE,QAAA,EAAU,KAAA,EAAO,KAAA,EAAO,CAAA,uBAAA,EAA0B,WAAW,CAAA,CAAA,EAAG;AAAA,EAC7E;AACA,EAAA,IAAI,CAAC,gBAAA,CAAiB,WAAW,CAAA,EAAG;AAChC,IAAA,OAAO,EAAE,QAAA,EAAU,KAAA,EAAO,KAAA,EAAO,8CAAA,EAA+C;AAAA,EACpF;AAGA,EAAA,MAAM,eAAA,GAAkB,MAAM,kBAAA,CAAmB,aAAA,EAAe,WAAW,CAAA;AAC3E,EAAA,IAAI,gBAAgB,QAAA,EAAU;AAC1B,IAAA,OAAO,eAAA;AAAA,EACX;AAIA,EAAA,GAAA,CAAI,IAAA,CAAK,EAAE,MAAA,EAAQ,WAAA,IAAe,0DAA0D,CAAA;AAE5F,EAAA,OAAO;AAAA,IACH,QAAA,EAAU,KAAA;AAAA,IACV,KAAA,EAAO;AAAA,GACX;AACJ;AAKA,eAAe,kBAAA,CACX,eACA,MAAA,EAC8B;AAI9B,EAAA,MAAM,GAAA,GAAM,WAAW,MAAM,CAAA,4BAAA,CAAA;AAE7B,EAAA,IAAI;AACA,IAAA,GAAA,CAAI,IAAA,CAAK,EAAE,GAAA,EAAI,EAAG,0BAA0B,CAAA;AAE5C,IAAA,MAAM,QAAA,GAAW,MAAM,KAAA,CAAM,GAAA,EAAK;AAAA,MAC9B,MAAA,EAAQ,KAAA;AAAA,MACR,OAAA,EAAS,EAAE,MAAA,EAAQ,YAAA,EAAa;AAAA;AAAA,MAEhC,QAAA,EAAU,QAAA;AAAA,MACV,MAAA,EAAQ,WAAA,CAAY,OAAA,CAAQ,qBAAqB;AAAA,KACpD,CAAA;AAED,IAAA,IAAI,SAAS,EAAA,EAAI;AACb,MAAA,MAAM,OAAA,GAAU,MAAM,QAAA,CAAS,IAAA,EAAK;AAEpC,MAAA,IAAI,OAAA,CAAQ,QAAA,CAAS,aAAa,CAAA,EAAG;AACjC,QAAA,GAAA,CAAI,IAAA,CAAK,EAAE,MAAA,EAAQ,GAAA,IAAO,yCAAyC,CAAA;AACnE,QAAA,OAAO;AAAA,UACH,QAAA,EAAU,IAAA;AAAA,UACV,MAAA,EAAQ,YAAA;AAAA,UACR;AAAA,SACJ;AAAA,MACJ,CAAA,MAAO;AACH,QAAA,GAAA,CAAI,IAAA,CAAK,EAAE,MAAA,EAAQ,GAAA,IAAO,oDAAoD,CAAA;AAAA,MAClF;AAAA,IACJ;AAAA,EACJ,SAAS,GAAA,EAAK;AACV,IAAA,GAAA,CAAI,KAAK,EAAE,KAAA,EAAO,GAAA,EAAK,GAAA,IAAO,iCAAiC,CAAA;AAAA,EACnE;AAEA,EAAA,OAAO;AAAA,IACH,QAAA,EAAU,KAAA;AAAA,IACV,KAAA,EAAO;AAAA,GACX;AACJ;AAKO,SAAS,8BAAA,CAA+B,eAAuB,MAAA,EAAwB;AAC1F,EAAA,MAAM,WAAA,GAAc,OAAO,OAAA,CAAQ,cAAA,EAAgB,EAAE,CAAA,CAAE,OAAA,CAAQ,OAAO,EAAE,CAAA;AAExE,EAAA,OAAO,8BAA8B,WAAW,CAAA;;AAAA;;AAAA,6BAAA,EAIrB,WAAW,CAAA;AAAA;;AAAA;AAAA,WAAA,EAI7B,WAAW;AAAA,mBAAA,EACH,aAAa;AAAA,cAAA,EAAA,iBAClB,IAAI,IAAA,EAAK,EAAE,WAAA,EAAa;;AAAA;AAAA;;AAAA;;AAAA;AAAA,sBAAA,EAQhB,WAAW;AAAA,UAAA,EACvB,aAAa;;AAAA;AAAA;;AAAA,8CAAA,CAAA;AAMzB;AAKO,SAAS,wBAAA,CAAyB,eAAuB,MAAA,EAAwB;AACpF,EAAA,MAAM,WAAA,GAAc,OAAO,OAAA,CAAQ,cAAA,EAAgB,EAAE,CAAA,CAAE,OAAA,CAAQ,OAAO,EAAE,CAAA;AAExE,EAAA,OAAO,CAAA;AAAA;;AAAA,QAAA,EAGD,WAAW;AAAA,gBAAA,EACH,aAAa;AAAA,WAAA,EAAA,iBAClB,IAAI,IAAA,EAAK,EAAE,WAAA,EAAa;;AAAA,qCAAA,EAEE,WAAW,CAAA;AAAA,oCAAA,EACZ,aAAa,CAAA;;AAAA;;AAAA;;AAAA;AAAA;AAAA,CAAA;AASnD;AAKO,SAAS,gBAAgB,MAAA,EAAwB;AACpD,EAAA,MAAM,WAAA,GAAc,OAAO,OAAA,CAAQ,cAAA,EAAgB,EAAE,CAAA,CAAE,OAAA,CAAQ,OAAO,EAAE,CAAA;AACxE,EAAA,OAAO,WAAW,WAAW,CAAA,4BAAA,CAAA;AACjC;AAKO,SAAS,cAAc,MAAA,EAAyB;AACnD,EAAA,MAAM,WAAA,GAAc,OAAO,OAAA,CAAQ,cAAA,EAAgB,EAAE,CAAA,CAAE,OAAA,CAAQ,OAAO,EAAE,CAAA;AAGxE,EAAA,MAAM,aAAA,GAAgB,8CAAA;AACtB,EAAA,OAAO,aAAA,CAAc,KAAK,WAAW,CAAA;AACzC;AAMO,SAAS,qBAAA,CACZ,aAAA,EACA,OAAA,EACA,aAAA,EACO;AACP,EAAA,IAAI,CAAC,aAAA,EAAe;AAChB,IAAA,OAAO,KAAA;AAAA,EACX;AAEA,EAAA,OAAO,aAAA,CAAc,SAAS,aAAa,CAAA;AAC/C;;;AC3NA,IAAMA,IAAAA,GAAM,aAAa,8BAA8B,CAAA;AASvD,IAAM,oBAAA,GAAuB,4BAAA;AAG7B,IAAM,iBAAA,GAAoB,EAAA;AAC1B,IAAM,kBAAA,GAAqB,EAAA;AAG3B,IAAM,gBAAA,GAAmB,GAAA;AAIzB,IAAM,kBAAA,GAAqB,qDAAA;AAE3B,SAAS,iBAAiB,GAAA,EAAsB;AAC5C,EAAA,IAAI;AACA,IAAA,MAAM,CAAA,GAAI,IAAI,GAAA,CAAI,GAAG,CAAA;AACrB,IAAA,OAAO,CAAA,CAAE,QAAA,KAAa,QAAA,IAAY,CAAA,CAAE,QAAA,KAAa,oBAAA;AAAA,EACrD,CAAA,CAAA,MAAQ;AACJ,IAAA,OAAO,KAAA;AAAA,EACX;AACJ;AAiDA,eAAsB,oBAAA,CAClB,eACA,QAAA,EACiC;AACjC,EAAAA,KAAI,IAAA,CAAK,EAAE,aAAA,EAAe,QAAA,IAAY,2BAA2B,CAAA;AAEjE,EAAA,IAAI,CAAC,kBAAA,CAAmB,IAAA,CAAK,QAAQ,CAAA,EAAG;AACpC,IAAA,OAAO,EAAE,QAAA,EAAU,KAAA,EAAO,KAAA,EAAO,gCAAA,EAAiC;AAAA,EACtE;AAEA,EAAA,IAAI;AAEA,IAAA,MAAM,WAAW,MAAM,KAAA;AAAA,MACnB,CAAA,6BAAA,EAAgC,kBAAA,CAAmB,QAAQ,CAAC,mBAAmB,iBAAiB,CAAA,CAAA;AAAA,MAChG;AAAA,QACI,OAAA,EAAS,EAAE,MAAA,EAAQ,gCAAA,EAAiC;AAAA,QACpD,MAAA,EAAQ,WAAA,CAAY,OAAA,CAAQ,gBAAgB;AAAA;AAChD,KACJ;AAEA,IAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AACd,MAAA,IAAI,QAAA,CAAS,WAAW,GAAA,EAAK;AACzB,QAAA,OAAO;AAAA,UACH,QAAA,EAAU,KAAA;AAAA,UACV,KAAA,EAAO,gBAAgB,QAAQ,CAAA,WAAA;AAAA,SACnC;AAAA,MACJ;AAEA,MAAA,OAAO;AAAA,QACH,QAAA,EAAU,KAAA;AAAA,QACV,OAAO,CAAA,kBAAA,EAAqB,QAAA,CAAS,MAAM,CAAA,CAAA,EAAI,SAAS,UAAU,CAAA;AAAA,OACtE;AAAA,IACJ;AAEA,IAAA,MAAM,KAAA,GAAS,MAAM,QAAA,CAAS,IAAA,EAAK;AAEnC,IAAAA,IAAAA,CAAI,KAAK,EAAE,QAAA,EAAU,WAAW,KAAA,CAAM,MAAA,IAAU,sBAAsB,CAAA;AAGtE,IAAA,KAAA,MAAW,IAAA,IAAQ,KAAA,CAAM,KAAA,CAAM,CAAA,EAAG,iBAAiB,CAAA,EAAG;AAElD,MAAA,IAAI,IAAA,CAAK,WAAA,EAAa,QAAA,CAAS,aAAa,CAAA,EAAG;AAC3C,QAAAA,IAAAA,CAAI,KAAK,EAAE,QAAA,EAAU,QAAQ,IAAA,CAAK,EAAA,IAAM,0CAA0C,CAAA;AAClF,QAAA,OAAO;AAAA,UACH,QAAA,EAAU,IAAA;AAAA,UACV,SAAS,IAAA,CAAK,QAAA;AAAA,UACd,QAAQ,IAAA,CAAK;AAAA,SACjB;AAAA,MACJ;AAGA,MAAA,MAAM,KAAA,GAAQ,OAAO,OAAA,CAAQ,IAAA,CAAK,KAAK,CAAA,CAAE,KAAA,CAAM,GAAG,kBAAkB,CAAA;AACpE,MAAA,KAAA,MAAW,CAAC,QAAA,EAAU,IAAI,CAAA,IAAK,KAAA,EAAO;AAElC,QAAA,IAAI,KAAK,OAAA,IAAW,IAAA,CAAK,OAAA,CAAQ,QAAA,CAAS,aAAa,CAAA,EAAG;AACtD,UAAAA,IAAAA,CAAI,IAAA;AAAA,YACA,EAAE,QAAA,EAAU,MAAA,EAAQ,IAAA,CAAK,IAAI,QAAA,EAAS;AAAA,YACtC;AAAA,WACJ;AACA,UAAA,OAAO;AAAA,YACH,QAAA,EAAU,IAAA;AAAA,YACV,SAAS,IAAA,CAAK,QAAA;AAAA,YACd,QAAQ,IAAA,CAAK;AAAA,WACjB;AAAA,QACJ;AAMA,QAAA,IAAI,CAAC,gBAAA,CAAiB,IAAA,CAAK,OAAO,CAAA,EAAG;AACjC,UAAAA,IAAAA,CAAI,IAAA;AAAA,YACA,EAAE,QAAA,EAAU,MAAA,EAAQ,IAAA,CAAK,OAAA,EAAQ;AAAA,YACjC;AAAA,WACJ;AACA,UAAA;AAAA,QACJ;AAEA,QAAA,IAAI;AACA,UAAA,MAAM,YAAA,GAAe,MAAM,KAAA,CAAM,IAAA,CAAK,OAAA,EAAS;AAAA,YAC3C,MAAA,EAAQ,WAAA,CAAY,OAAA,CAAQ,gBAAgB;AAAA,WAC/C,CAAA;AACD,UAAA,IAAI,aAAa,EAAA,EAAI;AACjB,YAAA,MAAM,OAAA,GAAU,MAAM,YAAA,CAAa,IAAA,EAAK;AACxC,YAAA,IAAI,OAAA,CAAQ,QAAA,CAAS,aAAa,CAAA,EAAG;AACjC,cAAAA,IAAAA,CAAI,IAAA;AAAA,gBACA,EAAE,QAAA,EAAU,MAAA,EAAQ,IAAA,CAAK,IAAI,QAAA,EAAS;AAAA,gBACtC;AAAA,eACJ;AACA,cAAA,OAAO;AAAA,gBACH,QAAA,EAAU,IAAA;AAAA,gBACV,SAAS,IAAA,CAAK,QAAA;AAAA,gBACd,QAAQ,IAAA,CAAK;AAAA,eACjB;AAAA,YACJ;AAAA,UACJ;AAAA,QACJ,SAAS,GAAA,EAAK;AACV,UAAAA,KAAI,IAAA,CAAK,EAAE,OAAO,GAAA,EAAK,QAAA,IAAY,mCAAmC,CAAA;AAAA,QAC1E;AAAA,MACJ;AAAA,IACJ;AAEA,IAAAA,IAAAA,CAAI,IAAA,CAAK,EAAE,QAAA,IAAY,4BAA4B,CAAA;AACnD,IAAA,OAAO;AAAA,MACH,QAAA,EAAU,KAAA;AAAA,MACV,KAAA,EAAO;AAAA,KACX;AAAA,EACJ,SAAS,GAAA,EAAK;AACV,IAAAA,KAAI,KAAA,CAAM,EAAE,OAAO,GAAA,EAAK,QAAA,IAAY,kCAAkC,CAAA;AACtE,IAAA,OAAO;AAAA,MACH,QAAA,EAAU,KAAA;AAAA,MACV,KAAA,EAAO,GAAA,YAAe,KAAA,GAAQ,GAAA,CAAI,OAAA,GAAU;AAAA,KAChD;AAAA,EACJ;AACJ;AAKO,SAAS,kCAAkC,aAAA,EAA+B;AAC7E,EAAA,OAAO,CAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA,mBAAA,EAQU,aAAa;;AAAA;AAAA;;AAAA,uEAAA,CAAA;AAMlC;AAKO,SAAS,oBAAA,CAAqB,eAAuB,QAAA,EAA0B;AAClF,EAAA,OAAO,CAAA;;AAAA,oCAAA,EAE2B,QAAQ,CAAA;;AAAA,sBAAA,EAEtB,aAAa,CAAA;;AAAA,eAAA,EAAA,iBAEpB,IAAI,IAAA,EAAK,EAAE,WAAA,EAAa;;AAAA;AAAA,mBAAA,EAGpB,QAAQ;AAAA,8CAAA,EACmB,aAAa;;AAAA;;AAAA;AAAA,CAAA;AAM7D;AAMO,SAAS,wBAAA,CACZ,aAAA,EACA,SAAA,EACA,WAAA,EACO;AACP,EAAA,OAAO,WAAA,CAAY,IAAA,CAAK,CAAC,IAAA,KAAS;AAE9B,IAAA,IAAI,IAAA,CAAK,WAAA,EAAa,QAAA,CAAS,aAAa,CAAA,EAAG;AAC3C,MAAA,OAAO,IAAA;AAAA,IACX;AAGA,IAAA,OAAO,MAAA,CAAO,MAAA,CAAO,IAAA,CAAK,KAAK,CAAA,CAAE,IAAA,CAAK,CAAC,IAAA,KAAS,IAAA,CAAK,OAAA,EAAS,QAAA,CAAS,aAAa,CAAC,CAAA;AAAA,EACzF,CAAC,CAAA;AACL;AAOO,SAAS,kBAAA,GAA6B;AACzC,EAAA,OAAO,0BAAA;AACX;AC1NO,IAAM,cAAA,GAA2B;AAAA,EACpC,sBAAA;AAAA,EACA,eAAA;AAAA,EACA,0BAAA;AAAA,EACA;AACJ,CAAA;;;AC7BA,IAAMA,IAAAA,GAAM,aAAa,6BAA6B,CAAA;AAEtD,IAAM,SAAA,GAAY,gBAAA;AAElB,SAAS,MAAM,KAAA,EAA2B;AACtC,EAAA,IAAI,GAAA,GAAM,EAAA;AACV,EAAA,KAAA,MAAW,CAAA,IAAK,OAAO,GAAA,IAAO,CAAA,CAAE,SAAS,EAAE,CAAA,CAAE,QAAA,CAAS,CAAA,EAAG,GAAG,CAAA;AAC5D,EAAA,OAAO,GAAA;AACX;AAEA,SAAS,QAAQ,GAAA,EAAyB;AACtC,EAAA,IAAI,IAAI,MAAA,GAAS,CAAA,KAAM,GAAG,MAAM,IAAI,MAAM,gBAAgB,CAAA;AAC1D,EAAA,MAAM,GAAA,GAAM,IAAI,UAAA,CAAW,GAAA,CAAI,SAAS,CAAC,CAAA;AACzC,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,GAAA,CAAI,QAAQ,CAAA,EAAA,EAAK;AACjC,IAAA,GAAA,CAAI,CAAC,CAAA,GAAI,QAAA,CAAS,GAAA,CAAI,KAAA,CAAM,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,CAAA,GAAI,CAAC,CAAA,EAAG,EAAE,CAAA;AAAA,EACrD;AACA,EAAA,OAAO,GAAA;AACX;AAgBA,SAAS,UAAU,IAAA,EAAsB;AACrC,EAAA,MAAM,OAAA,GAAU,KAAK,IAAA,EAAK;AAC1B,EAAA,IAAI,SAAA,CAAU,IAAA,CAAK,OAAA,CAAQ,WAAA,EAAa,CAAA,EAAG;AACvC,IAAA,OAAO,QAAQ,WAAA,EAAY;AAAA,EAC/B;AACA,EAAA,IAAI,CAAC,OAAA,CAAQ,WAAA,EAAY,CAAE,UAAA,CAAW,OAAO,CAAA,EAAG;AAC5C,IAAA,MAAM,IAAI,MAAM,CAAA,aAAA,EAAgB,OAAA,CAAQ,MAAM,CAAA,EAAG,EAAE,CAAC,CAAA,MAAA,CAAG,CAAA;AAAA,EAC3D;AAEA,EAAA,MAAM,UAAUC,WAAA,CAAO,MAAA,CAAO,OAAA,CAAQ,WAAA,IAAwC,IAAI,CAAA;AAClF,EAAA,IAAI,OAAA,CAAQ,WAAW,MAAA,EAAQ;AAC3B,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,2CAAA,EAA8C,OAAA,CAAQ,MAAM,CAAA,CAAE,CAAA;AAAA,EAClF;AACA,EAAA,MAAM,KAAA,GAAQA,WAAA,CAAO,SAAA,CAAU,OAAA,CAAQ,KAAK,CAAA;AAC5C,EAAA,IAAI,KAAA,CAAM,WAAW,EAAA,EAAI;AACrB,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,6BAAA,EAAgC,KAAA,CAAM,MAAM,CAAA,CAAE,CAAA;AAAA,EAClE;AACA,EAAA,OAAO,KAAA,CAAM,UAAA,CAAW,IAAA,CAAK,KAAK,CAAC,CAAA;AACvC;AAKA,eAAe,iBAAiB,OAAA,EAMN;AACtB,EAAA,MAAM,EAAE,SAAS,KAAA,EAAO,MAAA,EAAQ,SAAS,cAAA,EAAgB,KAAA,GAAQ,KAAI,GAAI,OAAA;AACzE,EAAA,MAAM,SAAuB,EAAC;AAE9B,EAAAD,IAAAA,CAAI,KAAK,EAAE,OAAA,EAAS,OAAO,UAAA,EAAY,MAAA,CAAO,MAAA,EAAO,EAAG,2BAA2B,CAAA;AAEnF,EAAA,MAAM,aAAA,GAAgB,MAAA,CAAO,GAAA,CAAI,OAAO,QAAA,KAAa;AACjD,IAAA,IAAI,EAAA,GAAuB,IAAA;AAE3B,IAAA,IAAI;AACA,MAAA,EAAA,GAAK,IAAI,UAAU,QAAQ,CAAA;AAC3B,MAAA,MAAM,KAAA,GAAQ,EAAA;AAEd,MAAA,MAAM,IAAI,OAAA,CAAc,CAAC,OAAA,EAAS,MAAA,KAAW;AACzC,QAAA,MAAM,OAAA,GAAU,WAAW,MAAM;AAC7B,UAAA,KAAA,CAAM,KAAA,EAAM;AACZ,UAAA,MAAA,CAAO,IAAI,KAAA,CAAM,eAAe,CAAC,CAAA;AAAA,QACrC,GAAG,GAAK,CAAA;AAER,QAAA,MAAM,UAAU,MAAM;AAClB,UAAA,YAAA,CAAa,OAAO,CAAA;AACpB,UAAA,KAAA,CAAM,KAAA,EAAM;AAAA,QAChB,CAAA;AAEA,QAAA,KAAA,CAAM,SAAS,MAAM;AACjB,UAAA,MAAM,cAAA,GAAiB,CAAA,OAAA,EAAU,IAAA,CAAK,GAAA,EAAK,CAAA,CAAA;AAC3C,UAAA,MAAM,MAAA,GAAkC;AAAA,YACpC,OAAA;AAAA,YACA,KAAA;AAAA,YACA;AAAA,WACJ;AAEA,UAAA,KAAA,CAAM,IAAA,CAAK,KAAK,SAAA,CAAU,CAAC,OAAO,cAAA,EAAgB,MAAM,CAAC,CAAC,CAAA;AAAA,QAC9D,CAAA;AAEA,QAAA,KAAA,CAAM,SAAA,GAAY,CAAC,GAAA,KAAQ;AACvB,UAAA,IAAI;AACA,YAAA,MAAM,IAAA,GAAO,IAAA,CAAK,KAAA,CAAM,GAAA,CAAI,IAAI,CAAA;AAChC,YAAA,IAAI,IAAA,CAAK,CAAC,CAAA,KAAM,OAAA,EAAS;AACrB,cAAA,MAAM,KAAA,GAAQ,KAAK,CAAC,CAAA;AAGpB,cAAA,IAAI,MAAA,EAAQ;AACR,gBAAA,IAAI,KAAA,CAAM,OAAA,CAAQ,QAAA,CAAS,MAAM,CAAA,EAAG;AAChC,kBAAA,MAAA,CAAO,KAAK,KAAK,CAAA;AAAA,gBACrB;AAAA,cACJ,CAAA,MAAO;AACH,gBAAA,MAAA,CAAO,KAAK,KAAK,CAAA;AAAA,cACrB;AAAA,YACJ,CAAA,MAAA,IAAW,IAAA,CAAK,CAAC,CAAA,KAAM,MAAA,EAAQ;AAC3B,cAAA,OAAA,EAAQ;AACR,cAAA,OAAA,EAAQ;AAAA,YACZ;AAAA,UACJ,SAAS,GAAA,EAAK;AACV,YAAAA,IAAAA,CAAI,MAAM,EAAE,KAAA,EAAO,UAAU,KAAA,EAAO,GAAA,IAAO,uBAAuB,CAAA;AAAA,UACtE;AAAA,QACJ,CAAA;AAEA,QAAA,KAAA,CAAM,OAAA,GAAU,CAAC,GAAA,KAAQ;AACrB,UAAAA,IAAAA,CAAI,MAAM,EAAE,KAAA,EAAO,UAAU,KAAA,EAAO,GAAA,IAAO,8BAA8B,CAAA;AACzE,UAAA,OAAA,EAAQ;AACR,UAAA,MAAA,CAAO,GAAG,CAAA;AAAA,QACd,CAAA;AAEA,QAAA,KAAA,CAAM,UAAU,MAAM;AAClB,UAAA,YAAA,CAAa,OAAO,CAAA;AAAA,QACxB,CAAA;AAAA,MACJ,CAAC,CAAA;AAAA,IACL,SAAS,GAAA,EAAK;AACV,MAAAA,IAAAA,CAAI,MAAM,EAAE,KAAA,EAAO,UAAU,KAAA,EAAO,GAAA,IAAO,uBAAuB,CAAA;AAAA,IACtE,CAAA,SAAE;AACE,MAAA,IAAI,EAAA,IAAM,EAAA,CAAG,UAAA,KAAe,SAAA,CAAU,MAAA,EAAQ;AAC1C,QAAA,EAAA,CAAG,KAAA,EAAM;AAAA,MACb;AAAA,IACJ;AAAA,EACJ,CAAC,CAAA;AAED,EAAA,MAAM,OAAA,CAAQ,WAAW,aAAa,CAAA;AAEtC,EAAAA,KAAI,IAAA,CAAK,EAAE,YAAY,MAAA,CAAO,MAAA,IAAU,gBAAgB,CAAA;AAExD,EAAA,OAAO,MAAA;AACX;AAaA,eAAe,0BAA0B,KAAA,EAAqC;AAC1E,EAAA,IAAI;AACA,IAAA,IACI,CAAC,KAAA,CAAM,EAAA,IACP,CAAC,KAAA,CAAM,OACP,CAAC,KAAA,CAAM,MAAA,IACP,OAAO,MAAM,EAAA,KAAO,QAAA,IACpB,OAAO,KAAA,CAAM,QAAQ,QAAA,IACrB,OAAO,KAAA,CAAM,MAAA,KAAW,YACxB,KAAA,CAAM,GAAA,CAAI,MAAA,KAAW,GAAA,IACrB,CAAC,SAAA,CAAU,IAAA,CAAK,KAAA,CAAM,EAAE,KACxB,CAAC,SAAA,CAAU,IAAA,CAAK,KAAA,CAAM,MAAM,CAAA,EAC9B;AACE,MAAA,OAAO,KAAA;AAAA,IACX;AAKA,IAAA,MAAM,QAAA,GAAW,KAAK,SAAA,CAAU;AAAA,MAC5B,CAAA;AAAA,MACA,KAAA,CAAM,MAAA;AAAA,MACN,KAAA,CAAM,UAAA;AAAA,MACN,KAAA,CAAM,IAAA;AAAA,MACN,KAAA,CAAM,QAAQ,EAAC;AAAA,MACf,MAAM,OAAA,IAAW;AAAA,KACpB,CAAA;AACD,IAAA,MAAM,UAAA,GAAa,MAAME,cAAA,CAAO,IAAI,aAAY,CAAE,MAAA,CAAO,QAAQ,CAAC,CAAC,CAAA;AACnE,IAAA,IAAI,UAAA,KAAe,KAAA,CAAM,EAAA,CAAG,WAAA,EAAY,EAAG;AACvC,MAAAF,IAAAA,CAAI,IAAA;AAAA,QACA,EAAE,OAAA,EAAS,KAAA,CAAM,EAAA,EAAI,UAAA,EAAW;AAAA,QAChC;AAAA,OACJ;AACA,MAAA,OAAO,KAAA;AAAA,IACX;AAEA,IAAA,OAAOG,oBAAA,CAAQ,MAAA,CAAO,OAAA,CAAQ,KAAA,CAAM,GAAG,CAAA,EAAG,OAAA,CAAQ,KAAA,CAAM,EAAE,CAAA,EAAG,OAAA,CAAQ,KAAA,CAAM,MAAM,CAAC,CAAA;AAAA,EACtF,SAAS,GAAA,EAAK;AACV,IAAAH,IAAAA,CAAI,IAAA,CAAK,EAAE,GAAA,IAAO,sBAAsB,CAAA;AACxC,IAAA,OAAO,KAAA;AAAA,EACX;AACJ;AAeA,eAAsB,mBAAA,CAClB,aAAA,EACA,IAAA,EACA,MAAA,EACgC;AAChC,EAAAA,KAAI,IAAA,CAAK,EAAE,aAAA,EAAe,IAAA,IAAQ,0BAA0B,CAAA;AAE5D,EAAA,IAAI;AAEA,IAAA,MAAM,SAAA,GAAY,UAAU,IAAI,CAAA;AAGhC,IAAA,MAAM,MAAA,GAAS,MAAM,gBAAA,CAAiB;AAAA,MAClC,OAAA,EAAS,CAAC,SAAS,CAAA;AAAA,MACnB,KAAA,EAAO,CAAC,CAAC,CAAA;AAAA;AAAA,MACT,MAAA,EAAQ,aAAA;AAAA,MACR;AAAA,KACH,CAAA;AAED,IAAA,IAAI,MAAA,CAAO,WAAW,CAAA,EAAG;AACrB,MAAAA,IAAAA,CAAI,IAAA,CAAK,EAAE,IAAA,IAAQ,8BAA8B,CAAA;AACjD,MAAA,OAAO;AAAA,QACH,QAAA,EAAU,KAAA;AAAA,QACV,KAAA,EAAO;AAAA,OACX;AAAA,IACJ;AAGA,IAAA,KAAA,MAAW,SAAS,MAAA,EAAQ;AACxB,MAAA,IAAI,KAAA,CAAM,OAAA,CAAQ,QAAA,CAAS,aAAa,CAAA,EAAG;AAEvC,QAAA,MAAM,OAAA,GAAU,MAAM,yBAAA,CAA0B,KAAK,CAAA;AAErD,QAAA,IAAI,OAAA,EAAS;AACT,UAAAA,IAAAA,CAAI,KAAK,EAAE,IAAA,EAAM,SAAS,KAAA,CAAM,EAAA,IAAM,yBAAyB,CAAA;AAC/D,UAAA,OAAO;AAAA,YACH,QAAA,EAAU,IAAA;AAAA,YACV;AAAA,WACJ;AAAA,QACJ,CAAA,MAAO;AACH,UAAAA,IAAAA,CAAI,KAAK,EAAE,IAAA,EAAM,SAAS,KAAA,CAAM,EAAA,IAAM,yBAAyB,CAAA;AAAA,QACnE;AAAA,MACJ;AAAA,IACJ;AAEA,IAAA,OAAO;AAAA,MACH,QAAA,EAAU,KAAA;AAAA,MACV,KAAA,EAAO;AAAA,KACX;AAAA,EACJ,SAAS,GAAA,EAAK;AACV,IAAAA,KAAI,KAAA,CAAM,EAAE,OAAO,GAAA,EAAK,IAAA,IAAQ,iCAAiC,CAAA;AACjE,IAAA,OAAO;AAAA,MACH,QAAA,EAAU,KAAA;AAAA,MACV,KAAA,EAAO,GAAA,YAAe,KAAA,GAAQ,GAAA,CAAI,OAAA,GAAU;AAAA,KAChD;AAAA,EACJ;AACJ;AAKO,SAAS,iCAAiC,aAAA,EAA+B;AAC5E,EAAA,OAAO,CAAA;;AAAA;AAAA;;AAAA,yCAAA,EAKgC,aAAa;;AAAA;AAAA;;AAAA,8FAAA,CAAA;AAMxD;AAMO,SAAS,uBAAA,CACZ,aAAA,EACA,IAAA,EACA,YAAA,EACO;AACP,EAAA,MAAM,SAAA,GAAY,UAAU,IAAI,CAAA;AAEhC,EAAA,OAAO,YAAA,CAAa,IAAA;AAAA,IAChB,CAAC,KAAA,KACG,KAAA,CAAM,MAAA,KAAW,SAAA,IAAa,KAAA,CAAM,IAAA,KAAS,CAAA,IAAK,KAAA,CAAM,OAAA,CAAQ,QAAA,CAAS,aAAa;AAAA,GAC9F;AACJ;;;ACjUA,IAAMA,IAAAA,GAAM,aAAa,+BAA+B,CAAA;AAoBxD,eAAsB,qBAAA,CAClB,aAAA,EACA,MAAA,EACA,QAAA,EACkC;AAClC,EAAAA,KAAI,IAAA,CAAK,EAAE,eAAe,MAAA,EAAQ,QAAA,IAAY,4BAA4B,CAAA;AAE1E,EAAA,IAAI,CAAC,QAAA,EAAU;AACX,IAAA,OAAO;AAAA,MACH,QAAA,EAAU,KAAA;AAAA,MACV,KAAA,EAAO;AAAA,KACX;AAAA,EACJ;AAEA,EAAA,IAAI;AAEA,IAAA,MAAM,OAAA,GAAU,eAAe,QAAQ,CAAA;AAEvC,IAAA,IAAI,CAAC,OAAA,EAAS;AACV,MAAA,OAAO;AAAA,QACH,QAAA,EAAU,KAAA;AAAA,QACV,KAAA,EAAO;AAAA,OACX;AAAA,IACJ;AAIA,IAAAA,KAAI,IAAA,CAAK,EAAE,MAAA,EAAQ,OAAA,IAAW,8BAA8B,CAAA;AAE5D,IAAA,OAAO;AAAA,MACH,QAAA,EAAU,KAAA;AAAA,MACV,QAAA;AAAA,MACA,OAAA;AAAA,MACA,KAAA,EAAO;AAAA,KACX;AAAA,EACJ,SAAS,GAAA,EAAK;AACV,IAAAA,KAAI,KAAA,CAAM,EAAE,OAAO,GAAA,EAAK,MAAA,IAAU,mCAAmC,CAAA;AACrE,IAAA,OAAO;AAAA,MACH,QAAA,EAAU,KAAA;AAAA,MACV,KAAA,EAAO,GAAA,YAAe,KAAA,GAAQ,GAAA,CAAI,OAAA,GAAU;AAAA,KAChD;AAAA,EACJ;AACJ;AAKA,SAAS,eAAe,GAAA,EAA4B;AAMhD,EAAA,MAAM,QAAA,GAAW;AAAA,IACb,6CAAA;AAAA,IACA;AAAA,GACJ;AAEA,EAAA,KAAA,MAAW,WAAW,QAAA,EAAU;AAC5B,IAAA,MAAM,KAAA,GAAQ,GAAA,CAAI,KAAA,CAAM,OAAO,CAAA;AAC/B,IAAA,IAAI,KAAA,IAAS,KAAA,CAAM,CAAC,CAAA,EAAG;AACnB,MAAA,OAAO,MAAM,CAAC,CAAA;AAAA,IAClB;AAAA,EACJ;AAEA,EAAA,OAAO,IAAA;AACX;AAKO,SAAS,kCAAA,CAAmC,eAAuB,MAAA,EAAwB;AAC9F,EAAA,MAAM,WAAA,GAAc,OAAO,UAAA,CAAW,GAAG,IAAI,MAAA,CAAO,KAAA,CAAM,CAAC,CAAA,GAAI,MAAA;AAE/D,EAAA,OAAO,qCAAqC,WAAW,CAAA;;AAAA;AAAA;;AAAA,yCAAA,EAKhB,aAAa;;AAAA;AAAA;;AAAA;AAAA,oBAAA,EAMlC,WAAW,CAAA;;AAAA,kFAAA,CAAA;AAGjC;AAKO,SAAS,kBAAkB,aAAA,EAA+B;AAC7D,EAAA,OAAO,yCAAyC,aAAa;;AAAA;;AAAA,qBAAA,CAAA;AAKjE;AAKO,SAAS,qBAAqB,aAAA,EAA+B;AAChE,EAAA,MAAM,IAAA,GAAO,kBAAkB,aAAa,CAAA;AAC5C,EAAA,MAAM,OAAA,GAAU,mBAAmB,IAAI,CAAA;AAEvC,EAAA,OAAO,yCAAyC,OAAO,CAAA,CAAA;AAC3D;AAKO,SAAS,gBAAgB,GAAA,EAAsB;AAClD,EAAA,OAAO,cAAA,CAAe,GAAG,CAAA,KAAM,IAAA;AACnC;AAKO,SAAS,WAAA,CAAY,QAAgB,OAAA,EAAyB;AACjE,EAAA,MAAM,WAAA,GAAc,OAAO,UAAA,CAAW,GAAG,IAAI,MAAA,CAAO,KAAA,CAAM,CAAC,CAAA,GAAI,MAAA;AAC/D,EAAA,OAAO,CAAA,oBAAA,EAAuB,WAAW,CAAA,QAAA,EAAW,OAAO,CAAA,CAAA;AAC/D;AAMO,SAAS,iCAAA,CACZ,aAAA,EACA,MAAA,EACA,QAAA,EACM;AACN,EAAA,OAAO,CAAA;;AAAA,oBAAA,EAEW,QAAQ;AAAA,0BAAA,EACF,MAAM;AAAA,kCAAA,EACE,aAAa;AAAA;;AAAA,6CAAA,CAAA;AAIjD;;;AC7GA,eAAsB,cAAA,CAClB,aAAA,EACA,QAAA,EACA,OAAA,EAC2B;AAC3B,EAAA,MAAM,EAAE,QAAA,EAAU,UAAA,EAAW,GAAI,QAAA;AAGjC,EAAA,MAAM,YAAA,GAAe,CAAA,EAAG,QAAQ,CAAA,CAAA,EAAI,UAAU,CAAA,CAAA;AAC9C,EAAA,IAAI;AACA,IAAA,gBAAA,CAAiB,6BAA6B,YAAY,CAAA;AAAA,EAC9D,SAAS,GAAA,EAAK;AACV,IAAA,OAAO;AAAA,MACH,QAAA,EAAU,KAAA;AAAA,MACV,KAAA,EAAO,GAAA,YAAe,KAAA,GAAQ,GAAA,CAAI,OAAA,GAAU;AAAA,KAChD;AAAA,EACJ;AAEA,EAAA,IAAI;AACA,IAAA,QAAQ,QAAA;AAAU,MACd,KAAK,OAAA;AACD,QAAA,OAAO,MAAM,mBAAA,CAAoB,aAAA,EAAe,UAAA,EAAY,SAAS,MAAM,CAAA;AAAA,MAE/E,KAAK,QAAA;AACD,QAAA,OAAO,MAAM,oBAAA,CAAqB,aAAA,EAAe,UAAU,CAAA;AAAA,MAE/D,KAAK,SAAA;AACD,QAAA,OAAO,MAAM,qBAAA,CAAsB,aAAA,EAAe,UAAA,EAAY,SAAS,QAAQ,CAAA;AAAA,MAEnF,KAAK,KAAA;AACD,QAAA,OAAO,MAAM,iBAAA,CAAkB,aAAA,EAAe,UAAU,CAAA;AAAA,MAE5D,KAAK,OAAA;AAED,QAAA,OAAO;AAAA,UACH,QAAA,EAAU,KAAA;AAAA,UACV,KAAA,EAAO;AAAA,SACX;AAAA,MAEJ,KAAK,KAAA;AAED,QAAA,OAAO,MAAM,iBAAA,CAAkB,aAAA,EAAe,UAAU,CAAA;AAAA,MAE5D,KAAK,KAAA;AAED,QAAA,OAAO;AAAA,UACH,QAAA,EAAU,KAAA;AAAA,UACV,KAAA,EAAO;AAAA,SACX;AAAA,MAEJ;AACI,QAAA,OAAO;AAAA,UACH,QAAA,EAAU,KAAA;AAAA,UACV,KAAA,EAAO,qBAAqB,QAAQ,CAAA;AAAA,SACxC;AAAA;AACR,EACJ,SAAS,KAAA,EAAO;AAEZ,IAAA,oBAAA;AAAA,MACI,QAAA;AAAA,MACA,UAAA;AAAA,MACA,iBAAiB,KAAA,GAAQ,KAAA,GAAQ,IAAI,KAAA,CAAM,MAAA,CAAO,KAAK,CAAC,CAAA;AAAA,MACxD;AAAA,QACI;AAAA;AACJ,KACJ;AAEA,IAAA,OAAO;AAAA,MACH,QAAA,EAAU,KAAA;AAAA,MACV,KAAA,EAAO,KAAA,YAAiB,KAAA,GAAQ,KAAA,CAAM,OAAA,GAAU;AAAA,KACpD;AAAA,EACJ;AACJ;AAKO,SAAS,2BAAA,CACZ,eACA,QAAA,EACM;AACN,EAAA,MAAM,EAAE,QAAA,EAAU,UAAA,EAAW,GAAI,QAAA;AAEjC,EAAA,QAAQ,QAAA;AAAU,IACd,KAAK,OAAA;AACD,MAAA,OAAO,iCAAiC,aAAa,CAAA;AAAA,IAEzD,KAAK,QAAA;AACD,MAAA,OAAO,kCAAkC,aAAa,CAAA;AAAA,IAE1D,KAAK,SAAA;AACD,MAAA,OAAO,kCAAA,CAAmC,eAAe,UAAU,CAAA;AAAA,IAEvE,KAAK,KAAA;AACD,MAAA,OAAO,8BAAA,CAA+B,eAAe,UAAU,CAAA;AAAA,IAEnE,KAAK,OAAA;AACD,MAAA,OAAO,CAAA,uCAAA,CAAA;AAAA,IAEX,KAAK,KAAA;AACD,MAAA,OAAO,8BAAA,CAA+B,eAAe,UAAU,CAAA;AAAA,IAEnE,KAAK,KAAA;AACD,MAAA,OAAO,CAAA,qCAAA,CAAA;AAAA,IAEX;AACI,MAAA,OAAO,qBAAqB,QAAQ,CAAA,CAAA;AAAA;AAEhD;AAKO,SAAS,8BAA8B,QAAA,EAA2B;AACrE,EAAA,OAAO,CAAC,OAAA,EAAS,QAAA,EAAU,OAAO,KAAK,CAAA,CAAE,SAAS,QAAQ,CAAA;AAC9D;AAKO,SAAS,2BAA2B,QAAA,EAA2B;AAClE,EAAA,OAAO,CAAC,SAAA,EAAW,OAAO,CAAA,CAAE,SAAS,QAAQ,CAAA;AACjD;AAKA,eAAsB,mBAAA,CAClB,aAAA,EACA,UAAA,EACA,OAAA,EAIwC;AACxC,EAAA,MAAM,OAAA,uBAAc,GAAA,EAAgC;AAEpD,EAAA,MAAM,OAAA,CAAQ,GAAA;AAAA,IACV,UAAA,CAAW,GAAA,CAAI,OAAO,QAAA,KAAa;AAC/B,MAAA,MAAM,MAAM,CAAA,EAAG,QAAA,CAAS,QAAQ,CAAA,CAAA,EAAI,SAAS,UAAU,CAAA,CAAA;AACvD,MAAA,MAAM,QAAA,GAAW,OAAA,EAAS,SAAA,GAAY,QAAA,CAAS,UAAU,CAAA;AAEzD,MAAA,MAAM,MAAA,GAAS,MAAM,cAAA,CAAe,aAAA,EAAe,QAAA,EAAU;AAAA,QACzD,QAAA;AAAA,QACA,QAAQ,OAAA,EAAS;AAAA,OACpB,CAAA;AAED,MAAA,OAAA,CAAQ,GAAA,CAAI,KAAK,MAAM,CAAA;AAAA,IAC3B,CAAC;AAAA,GACL;AAEA,EAAA,OAAO,OAAA;AACX;AAKO,SAAS,uBAAuB,OAAA,EAKrC;AACE,EAAA,IAAI,QAAA,GAAW,CAAA;AACf,EAAA,IAAI,MAAA,GAAS,CAAA;AACb,EAAA,IAAI,OAAA,GAAU,CAAA;AAEd,EAAA,KAAA,MAAW,MAAA,IAAU,OAAA,CAAQ,MAAA,EAAO,EAAG;AACnC,IAAA,IAAI,OAAO,QAAA,EAAU;AACjB,MAAA,QAAA,EAAA;AAAA,IACJ,CAAA,MAAA,IAAW,MAAA,CAAO,KAAA,EAAO,QAAA,CAAS,iBAAiB,CAAA,EAAG;AAClD,MAAA,OAAA,EAAA;AAAA,IACJ,CAAA,MAAO;AACH,MAAA,MAAA,EAAA;AAAA,IACJ;AAAA,EACJ;AAEA,EAAA,OAAO;AAAA,IACH,OAAO,OAAA,CAAQ,IAAA;AAAA,IACf,QAAA;AAAA,IACA,MAAA;AAAA,IACA;AAAA,GACJ;AACJ","file":"identity.js","sourcesContent":["/**\n * Simple logger for SDK\n * Can be disabled in production builds\n */\n\n/**\n * Log level type\n */\nexport type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'none';\n\n/**\n * Logger context type\n */\nexport type LogContext = Record<string, unknown>;\n\n/**\n * Logger interface\n */\nexport interface Logger {\n debug: (contextOrMessage: string | LogContext, message?: string) => void;\n info: (contextOrMessage: string | LogContext, message?: string) => void;\n warn: (contextOrMessage: string | LogContext, message?: string) => void;\n error: (contextOrMessage: string | LogContext, message?: string) => void;\n}\n\nlet logLevel: LogLevel = 'none';\n\n/**\n * Set the global log level\n * @param level - Log level to set\n */\nexport function setLogLevel(level: LogLevel): void {\n logLevel = level;\n}\n\n/**\n * Get the current log level\n * @returns Current log level\n */\nexport function getLogLevel(): LogLevel {\n return logLevel;\n}\n\n/**\n * Create a namespaced logger\n * @param namespace - Logger namespace\n * @returns Logger instance\n */\nexport function createLogger(namespace: string): Logger {\n const shouldLog = (level: Exclude<LogLevel, 'none'>): boolean => {\n if (logLevel === 'none') return false;\n\n const levels: Exclude<LogLevel, 'none'>[] = ['debug', 'info', 'warn', 'error'];\n const currentLevelIndex = levels.indexOf(logLevel as Exclude<LogLevel, 'none'>);\n const messageLevelIndex = levels.indexOf(level);\n\n return messageLevelIndex >= currentLevelIndex;\n };\n\n const formatMessage = (msg: string): string => `[${namespace}] ${msg}`;\n\n return {\n debug: (contextOrMessage: string | LogContext, message?: string): void => {\n if (shouldLog('debug')) {\n if (typeof contextOrMessage === 'string') {\n console.debug(formatMessage(contextOrMessage));\n } else {\n console.debug(formatMessage(message ?? 'Debug'), contextOrMessage);\n }\n }\n },\n info: (contextOrMessage: string | LogContext, message?: string): void => {\n if (shouldLog('info')) {\n if (typeof contextOrMessage === 'string') {\n console.info(formatMessage(contextOrMessage));\n } else {\n console.info(formatMessage(message ?? 'Info'), contextOrMessage);\n }\n }\n },\n warn: (contextOrMessage: string | LogContext, message?: string): void => {\n if (shouldLog('warn')) {\n if (typeof contextOrMessage === 'string') {\n console.warn(formatMessage(contextOrMessage));\n } else {\n console.warn(formatMessage(message ?? 'Warning'), contextOrMessage);\n }\n }\n },\n error: (contextOrMessage: string | LogContext, message?: string): void => {\n if (shouldLog('error')) {\n if (typeof contextOrMessage === 'string') {\n console.error(formatMessage(contextOrMessage));\n } else {\n console.error(formatMessage(message ?? 'Error'), contextOrMessage);\n }\n }\n },\n };\n}\n\n/**\n * Log verification error\n * @param protocol - Identity protocol\n * @param identifier - Identity identifier\n * @param error - Error object\n * @param context - Additional context\n */\nexport function logVerificationError(\n protocol: string,\n identifier: string,\n error: Error,\n context?: LogContext\n): void {\n const logger = createLogger('identity-verification');\n logger.error(\n {\n protocol,\n identifier,\n error: error.message,\n stack: error.stack,\n ...context,\n },\n 'Verification failed'\n );\n}\n","/**\n * Simple in-memory rate limiter\n */\n\n/**\n * Rate limiter configuration\n */\nexport interface RateLimiter {\n /** Maximum number of requests allowed in the time window */\n readonly maxRequests: number;\n /** Time window in milliseconds */\n readonly windowMs: number;\n /** Internal request tracking map */\n readonly requests: Map<string, number[]>;\n}\n\n/**\n * Rate limit error\n */\nexport class RateLimitError extends Error {\n constructor(message = 'Rate limit exceeded. Please try again later.') {\n super(message);\n this.name = 'RateLimitError';\n }\n}\n\n/**\n * Create a rate limiter\n * @param maxRequests - Maximum requests allowed in the time window\n * @param windowMs - Time window in milliseconds\n * @returns Rate limiter instance\n */\nexport function createRateLimiter(maxRequests: number, windowMs: number): RateLimiter {\n if (maxRequests <= 0) {\n throw new Error('maxRequests must be greater than 0');\n }\n if (windowMs <= 0) {\n throw new Error('windowMs must be greater than 0');\n }\n\n return {\n maxRequests,\n windowMs,\n requests: new Map<string, number[]>(),\n };\n}\n\n/**\n * Check if a request is within rate limits\n * @param limiter - Rate limiter instance\n * @param key - Unique key for the request (e.g., user ID, IP address)\n * @returns True if request is allowed, false if rate limit exceeded\n */\nexport function checkRateLimit(limiter: RateLimiter, key: string): boolean {\n const now = Date.now();\n const requests = limiter.requests.get(key) ?? [];\n\n // Remove old requests outside the window\n const validRequests = requests.filter((timestamp) => now - timestamp < limiter.windowMs);\n\n // Check if limit exceeded\n if (validRequests.length >= limiter.maxRequests) {\n return false;\n }\n\n // Add new request\n validRequests.push(now);\n limiter.requests.set(key, validRequests);\n\n return true;\n}\n\n/**\n * Enforce rate limit, throwing an error if exceeded\n * @param limiter - Rate limiter instance\n * @param key - Unique key for the request\n * @throws {RateLimitError} If rate limit is exceeded\n */\nexport function enforceRateLimit(limiter: RateLimiter, key: string): void {\n if (!checkRateLimit(limiter, key)) {\n throw new RateLimitError();\n }\n}\n\n/**\n * Get remaining requests for a key\n * @param limiter - Rate limiter instance\n * @param key - Unique key for the request\n * @returns Number of remaining requests in the current window\n */\nexport function getRemainingRequests(limiter: RateLimiter, key: string): number {\n const now = Date.now();\n const requests = limiter.requests.get(key) ?? [];\n const validRequests = requests.filter((timestamp) => now - timestamp < limiter.windowMs);\n return Math.max(0, limiter.maxRequests - validRequests.length);\n}\n\n/**\n * Reset rate limit for a key\n * @param limiter - Rate limiter instance\n * @param key - Unique key to reset\n */\nexport function resetRateLimit(limiter: RateLimiter, key: string): void {\n limiter.requests.delete(key);\n}\n\n/**\n * Default rate limiter for identity verification\n * 5 requests per minute\n */\nexport const identityVerificationLimiter = createRateLimiter(5, 60000);\n","/**\n * DNS Identity Verification\n * Verifies that a user controls a DNS domain\n */\n\nimport { createLogger } from '../utils/logger';\n\nconst log = createLogger('identity-verification/dns');\n\n/** Public-internet check. Blocks literal private/loopback/link-local hosts\n * from being reached via the well-known file verifier — otherwise a caller\n * running this server-side is an SSRF primitive (cloud metadata, internal\n * services, etc.). We don't do full DNS resolution here — TLD/label shape\n * checks + literal-IP rejection are the cheap layers. Production verifiers\n * running this untrusted should add DNS-pinning on top.\n */\nconst PRIVATE_HOST_RE =\n /^(?:localhost|.*\\.local|.*\\.internal|.*\\.localhost|.*\\.lan|.*\\.home|.*\\.arpa|\\[?::1\\]?|\\[?::\\]?)$/i;\nconst PRIVATE_IPV4_RE =\n /^(?:10\\.|127\\.|169\\.254\\.|192\\.168\\.|172\\.(?:1[6-9]|2\\d|3[01])\\.|0\\.|100\\.(?:6[4-9]|[7-9]\\d|1[01]\\d|12[0-7])\\.|22[4-9]\\.|23\\d\\.|24\\d\\.|25[0-5]\\.)/;\n\n/** Tight fetch deadline so a slow target can't stall the verifier. */\nconst WELL_KNOWN_TIMEOUT_MS = 5_000;\n\nfunction isSafePublicHost(host: string): boolean {\n const h = host.toLowerCase();\n if (PRIVATE_HOST_RE.test(h)) return false;\n if (PRIVATE_IPV4_RE.test(h)) return false;\n // IPv6 literal — we don't enumerate every private range; reject bracketed\n // literals entirely (no legitimate verification target is an IPv6 literal).\n if (h.startsWith('[') || /^[0-9a-f:]+$/.test(h)) return false;\n return true;\n}\n\n/**\n * Verification result for DNS identity\n */\nexport interface DnsVerificationResult {\n verified: boolean;\n method?: 'well-known' | 'dns-txt';\n url?: string;\n error?: string;\n}\n\n/**\n * Verify that a user controls a DNS domain\n *\n * Verification methods (in order of preference):\n * 1. Check .well-known/orangecheck.txt file\n * 2. Check DNS TXT record (requires server-side DNS lookup)\n *\n * @param attestationId - The attestation ID to look for\n * @param domain - The domain name (without protocol)\n * @returns Verification result\n */\nexport async function verifyDnsIdentity(\n attestationId: string,\n domain: string\n): Promise<DnsVerificationResult> {\n log.info({ attestationId, domain }, 'Verifying DNS identity');\n\n // Clean domain (remove protocol if present)\n const cleanDomain = domain.replace(/^https?:\\/\\//, '').replace(/\\/$/, '');\n\n // Shape + SSRF guards before we ever hand this to fetch().\n if (!isValidDomain(cleanDomain)) {\n return { verified: false, error: `invalid domain format: ${cleanDomain}` };\n }\n if (!isSafePublicHost(cleanDomain)) {\n return { verified: false, error: 'domain resolves to a disallowed private host' };\n }\n\n // Try .well-known file first\n const wellKnownResult = await checkWellKnownFile(attestationId, cleanDomain);\n if (wellKnownResult.verified) {\n return wellKnownResult;\n }\n\n // DNS TXT record check requires server-side implementation\n // For now, return instructions\n log.info({ domain: cleanDomain }, 'Well-known file not found, DNS TXT check not implemented');\n\n return {\n verified: false,\n error: 'Verification file not found. Please create .well-known/orangecheck.txt',\n };\n}\n\n/**\n * Check .well-known/orangecheck.txt file\n */\nasync function checkWellKnownFile(\n attestationId: string,\n domain: string\n): Promise<DnsVerificationResult> {\n // HTTPS only. The old HTTP fallback meant a MITM / hostile network could\n // forge a passing verification; dropping it costs us nothing — any real\n // domain worth attesting to has TLS in 2026.\n const url = `https://${domain}/.well-known/orangecheck.txt`;\n\n try {\n log.info({ url }, 'Checking well-known file');\n\n const response = await fetch(url, {\n method: 'GET',\n headers: { Accept: 'text/plain' },\n // Don't follow redirects to other domains\n redirect: 'manual',\n signal: AbortSignal.timeout(WELL_KNOWN_TIMEOUT_MS),\n });\n\n if (response.ok) {\n const content = await response.text();\n\n if (content.includes(attestationId)) {\n log.info({ domain, url }, 'Found attestation ID in well-known file');\n return {\n verified: true,\n method: 'well-known',\n url,\n };\n } else {\n log.info({ domain, url }, 'Well-known file exists but no attestation ID found');\n }\n }\n } catch (err) {\n log.warn({ error: err, url }, 'Failed to fetch well-known file');\n }\n\n return {\n verified: false,\n error: 'Well-known file not found or does not contain attestation ID',\n };\n}\n\n/**\n * Get verification instructions for DNS identity\n */\nexport function getDnsVerificationInstructions(attestationId: string, domain: string): string {\n const cleanDomain = domain.replace(/^https?:\\/\\//, '').replace(/\\/$/, '');\n\n return `To verify your DNS domain (${cleanDomain}):\n\n**Method 1: .well-known file (Recommended)**\n\n1. Create a file at: https://${cleanDomain}/.well-known/orangecheck.txt\n2. Add this content to the file:\n\n OrangeCheck Attestation Verification\n Domain: ${cleanDomain}\n Attestation ID: ${attestationId}\n Timestamp: ${new Date().toISOString()}\n\n3. Make sure the file is publicly accessible\n4. Return here and click \"Verify\"\n\n**Method 2: DNS TXT record (Advanced)**\n\n1. Add a TXT record to your DNS:\n Name: _orangecheck.${cleanDomain}\n Value: ${attestationId}\n\n2. Wait for DNS propagation (up to 24 hours)\n3. Return here and click \"Verify\"\n\nNote: Method 1 is faster and easier to set up.`;\n}\n\n/**\n * Generate well-known file content\n */\nexport function generateWellKnownContent(attestationId: string, domain: string): string {\n const cleanDomain = domain.replace(/^https?:\\/\\//, '').replace(/\\/$/, '');\n\n return `OrangeCheck Attestation Verification\n=====================================\n\nDomain: ${cleanDomain}\nAttestation ID: ${attestationId}\nTimestamp: ${new Date().toISOString()}\n\nThis file verifies that the owner of ${cleanDomain} controls the Bitcoin\naddress associated with attestation ${attestationId}.\n\nLearn more: https://ochk.io\n\n---\n\nVerification method: .well-known/orangecheck.txt\nProtocol: OrangeCheck Protocol (OCP)\n`;\n}\n\n/**\n * Get the expected well-known file URL\n */\nexport function getWellKnownUrl(domain: string): string {\n const cleanDomain = domain.replace(/^https?:\\/\\//, '').replace(/\\/$/, '');\n return `https://${cleanDomain}/.well-known/orangecheck.txt`;\n}\n\n/**\n * Validate domain format\n */\nexport function isValidDomain(domain: string): boolean {\n const cleanDomain = domain.replace(/^https?:\\/\\//, '').replace(/\\/$/, '');\n\n // Basic domain validation\n const domainPattern = /^[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,}$/i;\n return domainPattern.test(cleanDomain);\n}\n\n/**\n * Check if a DNS identity is already verified\n * This is a quick check using cached data\n */\nexport function isDnsIdentityVerified(\n attestationId: string,\n _domain: string,\n cachedContent?: string\n): boolean {\n if (!cachedContent) {\n return false;\n }\n\n return cachedContent.includes(attestationId);\n}\n","/**\n * GitHub Identity Verification\n * Verifies that a user controls a GitHub identity by checking for gists\n */\n\nimport { createLogger } from '../utils/logger';\n\nconst log = createLogger('identity-verification/github');\n\n/**\n * Gist file bodies are fetched from `raw_url`. GitHub serves these from\n * `gist.githubusercontent.com`, but `raw_url` is *server-controlled* — a\n * malicious response could point it anywhere (internal IPs, 169.254.169.254,\n * file://, etc.) and our verifier would happily fetch it. Allow-list the\n * expected host to close the SSRF door.\n */\nconst GITHUB_GIST_RAW_HOST = 'gist.githubusercontent.com';\n\n/** Cap fan-out so a user with 1k gists × many files can't pin the verifier. */\nconst MAX_GISTS_SCANNED = 30;\nconst MAX_FILES_PER_GIST = 10;\n\n/** Tight per-request deadline. Github is usually under 300ms. */\nconst FETCH_TIMEOUT_MS = 5_000;\n\n/** Reject usernames that don't match GitHub's published allowed character set\n * before we interpolate them into a URL. */\nconst GITHUB_USERNAME_RE = /^[a-zA-Z0-9](?:[a-zA-Z0-9]|-(?=[a-zA-Z0-9])){0,38}$/;\n\nfunction isSafeGistRawUrl(raw: string): boolean {\n try {\n const u = new URL(raw);\n return u.protocol === 'https:' && u.hostname === GITHUB_GIST_RAW_HOST;\n } catch {\n return false;\n }\n}\n\n/**\n * Verification result for GitHub identity\n */\nexport interface GitHubVerificationResult {\n verified: boolean;\n gistUrl?: string;\n gistId?: string;\n error?: string;\n}\n\n/**\n * GitHub gist file structure\n */\ninterface GitHubGistFile {\n filename: string;\n type: string;\n language: string | null;\n raw_url: string;\n size: number;\n content?: string;\n}\n\n/**\n * GitHub gist structure\n */\ninterface GitHubGist {\n id: string;\n html_url: string;\n description: string;\n public: boolean;\n created_at: string;\n updated_at: string;\n files: Record<string, GitHubGistFile>;\n}\n\n/**\n * Verify that a user controls a GitHub identity\n *\n * Verification method:\n * 1. Query GitHub API for user's public gists\n * 2. Check if any gist file contains the attestation ID\n * 3. Return the gist URL as proof\n *\n * @param attestationId - The attestation ID to look for\n * @param username - The GitHub username\n * @returns Verification result with gist URL if verified\n */\nexport async function verifyGitHubIdentity(\n attestationId: string,\n username: string\n): Promise<GitHubVerificationResult> {\n log.info({ attestationId, username }, 'Verifying GitHub identity');\n\n if (!GITHUB_USERNAME_RE.test(username)) {\n return { verified: false, error: 'invalid GitHub username format' };\n }\n\n try {\n // Query GitHub API for user's gists\n const response = await fetch(\n `https://api.github.com/users/${encodeURIComponent(username)}/gists?per_page=${MAX_GISTS_SCANNED}`,\n {\n headers: { Accept: 'application/vnd.github.v3+json' },\n signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),\n }\n );\n\n if (!response.ok) {\n if (response.status === 404) {\n return {\n verified: false,\n error: `GitHub user '${username}' not found`,\n };\n }\n\n return {\n verified: false,\n error: `GitHub API error: ${response.status} ${response.statusText}`,\n };\n }\n\n const gists = (await response.json()) as GitHubGist[];\n\n log.info({ username, gistCount: gists.length }, 'Fetched GitHub gists');\n\n // Check each gist for attestation ID (capped fan-out)\n for (const gist of gists.slice(0, MAX_GISTS_SCANNED)) {\n // Check gist description\n if (gist.description?.includes(attestationId)) {\n log.info({ username, gistId: gist.id }, 'Found attestation ID in gist description');\n return {\n verified: true,\n gistUrl: gist.html_url,\n gistId: gist.id,\n };\n }\n\n // Check each file in the gist (capped)\n const files = Object.entries(gist.files).slice(0, MAX_FILES_PER_GIST);\n for (const [filename, file] of files) {\n // If content is available, check it\n if (file.content && file.content.includes(attestationId)) {\n log.info(\n { username, gistId: gist.id, filename },\n 'Found attestation ID in gist file'\n );\n return {\n verified: true,\n gistUrl: gist.html_url,\n gistId: gist.id,\n };\n }\n\n // If content not available, fetch it from raw_url — but only\n // if raw_url is a real GitHub-hosted URL. Without the allow-list\n // we're an SSRF primitive for anyone controlling the API\n // response (including via a compromised GitHub mirror / MITM).\n if (!isSafeGistRawUrl(file.raw_url)) {\n log.warn(\n { filename, rawUrl: file.raw_url },\n 'skipping gist file: raw_url failed allow-list'\n );\n continue;\n }\n\n try {\n const fileResponse = await fetch(file.raw_url, {\n signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),\n });\n if (fileResponse.ok) {\n const content = await fileResponse.text();\n if (content.includes(attestationId)) {\n log.info(\n { username, gistId: gist.id, filename },\n 'Found attestation ID in gist file (fetched)'\n );\n return {\n verified: true,\n gistUrl: gist.html_url,\n gistId: gist.id,\n };\n }\n }\n } catch (err) {\n log.warn({ error: err, filename }, 'Failed to fetch gist file content');\n }\n }\n }\n\n log.info({ username }, 'No verification gist found');\n return {\n verified: false,\n error: 'No gist found containing the attestation ID',\n };\n } catch (err) {\n log.error({ error: err, username }, 'Failed to verify GitHub identity');\n return {\n verified: false,\n error: err instanceof Error ? err.message : 'Unknown error',\n };\n }\n}\n\n/**\n * Get verification instructions for GitHub identity\n */\nexport function getGitHubVerificationInstructions(attestationId: string): string {\n return `To verify your GitHub identity:\n\n1. Go to https://gist.github.com\n2. Create a new public gist\n3. Add a file with any name (e.g., \"orangecheck.txt\")\n4. Paste this text into the file:\n\n OrangeCheck Attestation Verification\n Attestation ID: ${attestationId}\n\n5. Click \"Create public gist\"\n6. Return here and click \"Verify\" to check\n\nThe verification will search your public gists for this attestation ID.`;\n}\n\n/**\n * Generate a gist template for verification\n */\nexport function generateGistTemplate(attestationId: string, username: string): string {\n return `# OrangeCheck Attestation Verification\n\nI am verifying my GitHub identity (@${username}) for OrangeCheck.\n\n**Attestation ID:** \\`${attestationId}\\`\n\n**Timestamp:** ${new Date().toISOString()}\n\nThis gist proves that I control both:\n- GitHub account: @${username}\n- Bitcoin address associated with attestation ${attestationId}\n\n---\n\nLearn more about OrangeCheck: https://ochk.io\n`;\n}\n\n/**\n * Check if a GitHub identity is already verified\n * This is a quick check using cached gist data\n */\nexport function isGitHubIdentityVerified(\n attestationId: string,\n _username: string,\n cachedGists: GitHubGist[]\n): boolean {\n return cachedGists.some((gist) => {\n // Check description\n if (gist.description?.includes(attestationId)) {\n return true;\n }\n\n // Check files\n return Object.values(gist.files).some((file) => file.content?.includes(attestationId));\n });\n}\n\n/**\n * Get direct link to create a verification gist. The args are accepted for\n * API parity with other identity providers but unused — GitHub doesn't accept\n * pre-fill params on the gist creation page.\n */\nexport function getGistCreationUrl(): string {\n return 'https://gist.github.com/';\n}\n","/**\n * Nostr Integration for OrangeCheck Protocol\n *\n * Implements NIP-78 (Application-specific data) for attestation publishing\n */\n\nimport type { AttestationEnvelope, IdentityBinding, NostrEvent } from './types';\n\nimport { createLogger } from './utils/logger';\n\nconst log = createLogger('ocp/nostr');\n\n/**\n * NIP-78 event kind for OrangeCheck attestations\n */\nexport const ATTESTATION_EVENT_KIND = 30078;\n\n/**\n * Default Nostr relays for OrangeCheck attestation publishing and lookup.\n *\n * Well-known relays with good uptime, plus the first-party family relay.\n * `relay.ochk.io` was missing here while attest.ochk.io's verifier has always\n * read from it, so the SDK and the reference implementation were looking in\n * different places — the SDK could miss a family attestation that the hosted\n * API found. It runs a kind allowlist (30078-30087, 30110-30114) and canonical OC d-tag\n * prefixes, and is always co-published alongside the public relays, never the\n * only copy. See https://github.com/orangecheck/oc-relay-infra.\n *\n * Order matters only for readability; queries fan out to all of them and\n * merge. Pass your own `relays` to any query to override this entirely — the\n * protocol does not require trusting any particular relay.\n *\n * A NOTE ON `relay.damus.io`, deliberately kept. It is reachable from a browser\n * (measured ~391ms, real events returned) and NOT reachable from at least one\n * serverless runtime — Vercel's, where it fails to connect on every attempt,\n * consistently, over hours. Its NIP-11 declares no `auth_required`,\n * `payment_required` or `restricted_writes`, so this reads as datacenter-IP\n * filtering in front of it rather than policy, and it can change in either\n * direction without notice.\n *\n * It stays for three reasons: it is one of the most widely read relays, so an\n * attestation published there is findable by the most people; the browser is\n * the dominant path for this SDK (wallet signing happens there), and it works\n * there; and since FANOUT_DEADLINE_MS below is a SHARED budget, a relay that\n * fails to connect resolves immediately and costs no latency. Removing a relay\n * that works for most callers, to tidy a status field on one runtime, is the\n * wrong trade.\n *\n * If you run this SDK server-side and want certainty about coverage, pass\n * `relays` explicitly. Silent partial coverage — believing you reached four\n * relays when you reached three — is the real hazard here, not latency.\n */\nexport const DEFAULT_RELAYS: string[] = [\n 'wss://relay.damus.io',\n 'wss://nos.lol',\n 'wss://relay.snort.social',\n 'wss://relay.ochk.io',\n];\n\n/**\n * Wall-clock budget for a whole relay fan-out, shared by every relay in it.\n *\n * Queries below use Promise.allSettled, so a fan-out used to take as long as\n * its SLOWEST relay — and with a dead relay in DEFAULT_RELAYS that meant the\n * per-relay 10s timeout, every single call. Measured on 1.4.0:\n *\n * check({ addr }) with the old defaults 10.4s\n * check({ addr }) with the dead relay out 1.0s\n *\n * That is not merely slow, it broke a dependent outright: @orangecheck/gate\n * races check() against a 5s lookupTimeoutMs, so a 10.4s lookup lost the race\n * every time and no address-based gate decision could ever succeed.\n *\n * A shared deadline fixes the class: whichever relay is dead or slow next, a\n * fan-out costs at most this. Healthy relays answer well inside it (~1s for a\n * cold WebSocket plus a query). QUERY_TIMEOUT_MS remains the per-relay ceiling.\n *\n * The value is deliberately BELOW @orangecheck/gate's 5s default\n * lookupTimeoutMs, so a gate decision can still resolve in the worst case\n * rather than always losing its own race. Raising this above 5000 silently\n * breaks that dependent — change both together, or don't.\n */\nexport const FANOUT_DEADLINE_MS = 4000;\nconst QUERY_TIMEOUT_MS = 10000;\n\n/** Remaining budget for a relay, given the fan-out's shared deadline. */\nfunction budgetFor(deadlineAt: number): number {\n return Math.max(0, Math.min(QUERY_TIMEOUT_MS, deadlineAt - Date.now()));\n}\n\n/**\n * Create a NIP-78 event for an OrangeCheck attestation\n *\n * Kind: 30078 (Parameterized Replaceable Event)\n * d-tag: attestation_id (makes it replaceable by attestation ID)\n *\n * @param envelope - Attestation envelope\n * @param pubkey - Nostr public key (hex format)\n * @returns Unsigned Nostr event\n */\nexport function createAttestationEvent(\n envelope: AttestationEnvelope,\n pubkey: string\n): Omit<NostrEvent, 'id' | 'sig'> {\n // NIP-12 relays index SINGLE-LETTER tags only. A multi-letter tag like\n // `address` is stored verbatim and stays readable in the event, but it is\n // never indexed, so no `#address` filter can ever retrieve it. Without the\n // `t` tags below an attestation published by this SDK is invisible to\n // every by-address query on every relay — including attest.ochk.io's own\n // verifier, which queries `#t`. Keep both: `t` is what makes it findable,\n // `address` is human-readable diagnostics and is not load-bearing.\n const tags: string[][] = [\n ['d', envelope.attestation_id], // Parameterized replaceable event identifier (indexed)\n ['t', envelope.address], // Indexed btc-address tag — query with `#t`\n ['t', 'oc-attest'], // Family marker — `#t=[\"oc-attest\"]` for cross-cut discovery\n ['address', envelope.address], // Diagnostic / legacy — relay won't index this\n ['scheme', envelope.scheme], // Signature scheme (bip322 or legacy)\n ['issued_at', envelope.issued_at], // ISO timestamp\n ];\n\n // Add identity tags\n for (const identity of envelope.identities) {\n tags.push(['i', `${identity.protocol}:${identity.identifier}`]);\n }\n\n // Add expiration if present\n if (envelope.expires_at) {\n tags.push(['expires', envelope.expires_at]);\n }\n\n // Add relay hints if present\n if (envelope.relay_hints?.length) {\n for (const relay of envelope.relay_hints) {\n tags.push(['relay', relay]);\n }\n }\n\n // Content is the full attestation envelope as JSON\n const content = JSON.stringify(envelope, null, 2);\n\n return {\n kind: ATTESTATION_EVENT_KIND,\n tags,\n content,\n created_at: Math.floor(Date.parse(envelope.issued_at) / 1000),\n pubkey,\n };\n}\n\n/**\n * Parse identity bindings from Nostr event tags\n *\n * @param event - Nostr event\n * @returns Array of identity bindings\n */\nexport function parseIdentitiesFromEvent(event: NostrEvent): IdentityBinding[] {\n const identities: IdentityBinding[] = [];\n\n for (const tag of event.tags) {\n if (tag[0] === 'i' && tag[1]) {\n const colonIndex = tag[1].indexOf(':');\n if (colonIndex !== -1) {\n identities.push({\n protocol: tag[1].substring(0, colonIndex),\n identifier: tag[1].substring(colonIndex + 1),\n });\n }\n }\n }\n\n return identities;\n}\n\n/**\n * Extract attestation ID from Nostr event d-tag\n *\n * @param event - Nostr event\n * @returns Attestation ID or undefined if not found\n */\nexport function getAttestationIdFromEvent(event: NostrEvent): string | undefined {\n const dTag = event.tags.find((tag) => tag[0] === 'd');\n return dTag?.[1];\n}\n\n/**\n * Extract Bitcoin address from Nostr event tags\n *\n * @param event - Nostr event\n * @returns Bitcoin address or undefined if not found\n */\nexport function getAddressFromEvent(event: NostrEvent): string | undefined {\n const addressTag = event.tags.find((tag) => tag[0] === 'address');\n return addressTag?.[1];\n}\n\n/**\n * Publish a Nostr event to multiple relays\n * Returns array of relay URLs that successfully accepted the event\n */\nexport async function publishToRelays(\n event: NostrEvent,\n relays: string[] = DEFAULT_RELAYS\n): Promise<{ success: string[]; failed: string[] }> {\n const success: string[] = [];\n const failed: string[] = [];\n\n log.info({ eventId: event.id, relayCount: relays.length }, 'Publishing event to relays');\n\n // One shared deadline for the whole fan-out — see FANOUT_DEADLINE_MS.\n\n const deadlineAt = Date.now() + FANOUT_DEADLINE_MS;\n\n const publishPromises = relays.map(async (relayUrl) => {\n let ws: WebSocket | null = null;\n\n // One outcome per relay, first one wins. Without this a relay that\n // ACKs `OK true` and then emits an error or close event during\n // teardown lands in BOTH arrays, and the caller reports \"published\n // to 3 · rejected 3\" over the same three relays. The `failed.includes`\n // check this replaces only ever de-duplicated `failed` against\n // itself, so it could not catch a success/failure split.\n // attest.ochk.io's copy of this file has carried the guard for a\n // while; this is the back-port.\n let settled = false;\n const recordSuccess = (): void => {\n if (settled) return;\n settled = true;\n success.push(relayUrl);\n };\n const recordFailure = (): void => {\n if (settled) return;\n settled = true;\n failed.push(relayUrl);\n };\n\n try {\n // Connect to relay\n ws = new WebSocket(relayUrl);\n const wsRef = ws; // Capture for closures\n\n await new Promise<void>((resolve, reject) => {\n // One outer deadline covers connect + OK round-trip. The old\n // code cleared this on `onopen`, which meant a relay that\n // opened but never sent OK would hang the promise forever.\n const deadline = setTimeout(() => {\n wsRef.close();\n reject(new Error('Relay publish timeout'));\n }, budgetFor(deadlineAt));\n\n const cleanup = () => {\n clearTimeout(deadline);\n wsRef.close();\n };\n\n wsRef.onopen = () => {\n // Do NOT clear the deadline — we still need it in case\n // the relay never responds with OK.\n wsRef.send(JSON.stringify(['EVENT', event]));\n };\n\n wsRef.onmessage = (msg) => {\n try {\n const data = JSON.parse(msg.data);\n // Check for OK response\n if (data[0] === 'OK' && data[1] === event.id) {\n if (data[2] === true) {\n recordSuccess();\n cleanup();\n resolve();\n } else {\n log.warn(\n { relay: relayUrl, reason: data[3] },\n 'Relay rejected event'\n );\n recordFailure();\n cleanup();\n reject(new Error(data[3] || 'Relay rejected event'));\n }\n }\n } catch (err) {\n log.error(\n { relay: relayUrl, error: err },\n 'Failed to parse relay response'\n );\n recordFailure();\n cleanup();\n reject(err);\n }\n };\n\n wsRef.onerror = (err) => {\n log.error({ relay: relayUrl, error: err }, 'WebSocket error');\n recordFailure();\n cleanup();\n reject(err);\n };\n\n wsRef.onclose = () => {\n // Socket closed before we resolved/rejected — counts as\n // failure. clearTimeout here is safe (cleanup handles both).\n clearTimeout(deadline);\n };\n });\n } catch (err) {\n log.error({ relay: relayUrl, error: err }, 'Failed to publish to relay');\n recordFailure();\n } finally {\n // Ensure WebSocket is closed\n if (ws && ws.readyState !== WebSocket.CLOSED) {\n ws.close();\n }\n }\n });\n\n await Promise.allSettled(publishPromises);\n\n log.info({ successCount: success.length, failedCount: failed.length }, 'Publishing complete');\n\n return { success, failed };\n}\n\n/**\n * Query relays for attestations by attestation ID\n */\nexport async function queryByAttestationId(\n attestationId: string,\n relays: string[] = DEFAULT_RELAYS\n): Promise<NostrEvent[]> {\n const events: NostrEvent[] = [];\n\n log.info({ attestationId, relayCount: relays.length }, 'Querying relays for attestation');\n\n // One shared deadline for the whole fan-out — see FANOUT_DEADLINE_MS.\n\n const deadlineAt = Date.now() + FANOUT_DEADLINE_MS;\n\n const queryPromises = relays.map(async (relayUrl) => {\n let ws: WebSocket | null = null;\n\n try {\n ws = new WebSocket(relayUrl);\n const wsRef = ws; // Capture for closures\n\n await new Promise<void>((resolve, reject) => {\n const timeout = setTimeout(() => {\n wsRef.close();\n reject(new Error('Query timeout'));\n }, budgetFor(deadlineAt));\n\n const cleanup = () => {\n clearTimeout(timeout);\n wsRef.close();\n };\n\n wsRef.onopen = () => {\n // Send REQ message for kind 30078 with d-tag = attestation_id\n const subscriptionId = `ochk_${Date.now()}`;\n wsRef.send(\n JSON.stringify([\n 'REQ',\n subscriptionId,\n {\n kinds: [30078],\n '#d': [attestationId],\n limit: 1,\n },\n ])\n );\n };\n\n wsRef.onmessage = (msg) => {\n try {\n const data = JSON.parse(msg.data);\n if (data[0] === 'EVENT') {\n events.push(data[2]);\n } else if (data[0] === 'EOSE') {\n cleanup();\n resolve();\n }\n } catch (err) {\n log.error({ relay: relayUrl, error: err }, 'Failed to parse event');\n }\n };\n\n wsRef.onerror = (err) => {\n log.error({ relay: relayUrl, error: err }, 'WebSocket error during query');\n cleanup();\n reject(err);\n };\n\n wsRef.onclose = () => {\n clearTimeout(timeout);\n };\n });\n } catch (err) {\n log.error({ relay: relayUrl, error: err }, 'Failed to query relay');\n } finally {\n // Ensure WebSocket is closed\n if (ws && ws.readyState !== WebSocket.CLOSED) {\n ws.close();\n }\n }\n });\n\n await Promise.allSettled(queryPromises);\n\n log.info({ eventCount: events.length }, 'Query complete');\n\n return events;\n}\n\n/**\n * Query relays for attestations by Bitcoin address\n */\nexport async function queryByAddress(\n address: string,\n relays: string[] = DEFAULT_RELAYS\n): Promise<NostrEvent[]> {\n const events: NostrEvent[] = [];\n\n log.info({ address, relayCount: relays.length }, 'Querying relays for address');\n\n // One shared deadline for the whole fan-out — see FANOUT_DEADLINE_MS.\n\n const deadlineAt = Date.now() + FANOUT_DEADLINE_MS;\n\n const queryPromises = relays.map(async (relayUrl) => {\n try {\n const ws = new WebSocket(relayUrl);\n\n await new Promise<void>((resolve, reject) => {\n const timeout = setTimeout(() => {\n ws.close();\n reject(new Error('Query timeout'));\n }, budgetFor(deadlineAt));\n\n ws.onopen = () => {\n const subscriptionId = `ochk_addr_${Date.now()}`;\n ws.send(\n JSON.stringify([\n 'REQ',\n subscriptionId,\n {\n kinds: [30078],\n // `#t`, NOT `#address`: NIP-12 relays index\n // single-letter tags only, so `#address`\n // matched nothing on any relay and this\n // function returned empty for every real\n // attestation. attest.ochk.io's verifier has\n // queried `#t` since the same fix landed\n // there; this is the back-port.\n '#t': [address],\n limit: 50,\n },\n ])\n );\n };\n\n ws.onmessage = (msg) => {\n try {\n const data = JSON.parse(msg.data);\n if (data[0] === 'EVENT') {\n events.push(data[2]);\n } else if (data[0] === 'EOSE') {\n clearTimeout(timeout);\n ws.close();\n resolve();\n }\n } catch (err) {\n log.error({ relay: relayUrl, error: err }, 'Failed to parse event');\n }\n };\n\n ws.onerror = (err) => {\n clearTimeout(timeout);\n log.error({ relay: relayUrl, error: err }, 'WebSocket error during query');\n reject(err);\n };\n });\n } catch (err) {\n log.error({ relay: relayUrl, error: err }, 'Failed to query relay');\n }\n });\n\n await Promise.allSettled(queryPromises);\n\n log.info({ eventCount: events.length }, 'Query complete');\n\n return events;\n}\n\n/**\n * Query relays for attestations by identity (any protocol)\n */\nexport async function queryByIdentity(\n protocol: string,\n identifier: string,\n relays: string[] = DEFAULT_RELAYS\n): Promise<NostrEvent[]> {\n const events: NostrEvent[] = [];\n const identityTag = `${protocol}:${identifier}`;\n\n log.info({ protocol, identifier, relayCount: relays.length }, 'Querying relays for identity');\n\n // One shared deadline for the whole fan-out — see FANOUT_DEADLINE_MS.\n\n const deadlineAt = Date.now() + FANOUT_DEADLINE_MS;\n\n const queryPromises = relays.map(async (relayUrl) => {\n try {\n const ws = new WebSocket(relayUrl);\n\n await new Promise<void>((resolve, reject) => {\n const timeout = setTimeout(() => {\n ws.close();\n reject(new Error('Query timeout'));\n }, budgetFor(deadlineAt));\n\n ws.onopen = () => {\n const subscriptionId = `ochk_identity_${Date.now()}`;\n ws.send(\n JSON.stringify([\n 'REQ',\n subscriptionId,\n {\n kinds: [30078],\n '#i': [identityTag],\n limit: 10,\n },\n ])\n );\n };\n\n ws.onmessage = (msg) => {\n try {\n const data = JSON.parse(msg.data);\n if (data[0] === 'EVENT') {\n events.push(data[2]);\n } else if (data[0] === 'EOSE') {\n clearTimeout(timeout);\n ws.close();\n resolve();\n }\n } catch (err) {\n log.error({ relay: relayUrl, error: err }, 'Failed to parse event');\n }\n };\n\n ws.onerror = (err) => {\n clearTimeout(timeout);\n log.error({ relay: relayUrl, error: err }, 'WebSocket error during query');\n reject(err);\n };\n });\n } catch (err) {\n log.error({ relay: relayUrl, error: err }, 'Failed to query relay');\n }\n });\n\n await Promise.allSettled(queryPromises);\n\n log.info({ eventCount: events.length }, 'Query complete');\n\n return events;\n}\n\n/**\n * Verify attestation envelope integrity\n * Checks that the attestation ID matches the message hash\n */\nexport async function verifyAttestationEnvelope(envelope: AttestationEnvelope): Promise<boolean> {\n try {\n // Re-compute attestation ID from message\n const encoder = new TextEncoder();\n const data = encoder.encode(envelope.message);\n const hashBuffer = await crypto.subtle.digest('SHA-256', data);\n const hashArray = Array.from(new Uint8Array(hashBuffer));\n const computedId = hashArray.map((b) => b.toString(16).padStart(2, '0')).join('');\n\n return computedId === envelope.attestation_id;\n } catch (err) {\n log.error({ error: err }, 'Failed to verify attestation envelope');\n return false;\n }\n}\n\n/**\n * Parse attestation envelope from Nostr event content\n */\nexport function parseAttestationFromEvent(event: NostrEvent): AttestationEnvelope | null {\n try {\n const envelope = JSON.parse(event.content) as AttestationEnvelope;\n return envelope;\n } catch (err) {\n log.error({ eventId: event.id, error: err }, 'Failed to parse attestation from event');\n return null;\n }\n}\n","/**\n * Nostr Identity Verification\n * Verifies that a user controls a Nostr identity by checking for proof events\n *\n * Two previous bugs in this file made verification a lie:\n * 1. `npubToHex` returned the bech32 string unchanged when given an npub\n * (only the already-hex branch worked). Downstream authors filters used\n * \"npub1…\" as a literal Nostr pubkey, which no relay indexes — so the\n * search *always* returned zero events and fell through to \"no proof\".\n * Benign false-negative, except combined with (2).\n * 2. `verifyNostrEventSignature` returned `true` for any event with a\n * 128-char-hex-looking `sig`. No actual schnorr check. An attacker\n * could publish a note from any pubkey, drop someone else's attestation\n * ID into its content, and the verifier would accept.\n *\n * Fixed by decoding npubs with @scure/base's bech32 and verifying the event\n * signature with @noble/curves' schnorr over sha256(id-preimage).\n */\n\nimport type { NostrEvent } from '../types';\n\nimport { schnorr } from '@noble/curves/secp256k1.js';\nimport { sha256 } from '@noble/hashes/sha2.js';\nimport { bech32 } from '@scure/base';\n\nimport { DEFAULT_RELAYS } from '../nostr';\nimport { createLogger } from '../utils/logger';\n\nconst log = createLogger('identity-verification/nostr');\n\nconst HEX_64_RE = /^[0-9a-f]{64}$/;\n\nfunction toHex(bytes: Uint8Array): string {\n let out = '';\n for (const b of bytes) out += b.toString(16).padStart(2, '0');\n return out;\n}\n\nfunction fromHex(hex: string): Uint8Array {\n if (hex.length % 2 !== 0) throw new Error('odd-length hex');\n const out = new Uint8Array(hex.length / 2);\n for (let i = 0; i < out.length; i++) {\n out[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);\n }\n return out;\n}\n\n/**\n * Verification result for Nostr identity\n */\nexport interface NostrVerificationResult {\n verified: boolean;\n event?: NostrEvent;\n error?: string;\n}\n\n/**\n * Decode a Nostr public key in `npub1…` bech32 form (or pass through if\n * already 64-char hex). Throws on invalid input — callers should treat a\n * throw as \"not a valid npub\" and fail closed.\n */\nfunction npubToHex(npub: string): string {\n const trimmed = npub.trim();\n if (HEX_64_RE.test(trimmed.toLowerCase())) {\n return trimmed.toLowerCase();\n }\n if (!trimmed.toLowerCase().startsWith('npub1')) {\n throw new Error(`not an npub: ${trimmed.slice(0, 12)}…`);\n }\n // `@scure/base` bech32 decoder; unlimited length is fine for NIP-19.\n const decoded = bech32.decode(trimmed.toLowerCase() as `${string}1${string}`, 1023);\n if (decoded.prefix !== 'npub') {\n throw new Error(`bech32 prefix mismatch: expected npub, got ${decoded.prefix}`);\n }\n const bytes = bech32.fromWords(decoded.words);\n if (bytes.length !== 32) {\n throw new Error(`invalid npub payload length: ${bytes.length}`);\n }\n return toHex(Uint8Array.from(bytes));\n}\n\n/**\n * Query Nostr relays for events from a specific author\n */\nasync function queryNostrEvents(options: {\n authors: string[];\n kinds: number[];\n search?: string;\n relays?: string[];\n limit?: number;\n}): Promise<NostrEvent[]> {\n const { authors, kinds, search, relays = DEFAULT_RELAYS, limit = 100 } = options;\n const events: NostrEvent[] = [];\n\n log.info({ authors, kinds, relayCount: relays.length }, 'Querying Nostr for events');\n\n const queryPromises = relays.map(async (relayUrl) => {\n let ws: WebSocket | null = null;\n\n try {\n ws = new WebSocket(relayUrl);\n const wsRef = ws;\n\n await new Promise<void>((resolve, reject) => {\n const timeout = setTimeout(() => {\n wsRef.close();\n reject(new Error('Query timeout'));\n }, 10000);\n\n const cleanup = () => {\n clearTimeout(timeout);\n wsRef.close();\n };\n\n wsRef.onopen = () => {\n const subscriptionId = `verify_${Date.now()}`;\n const filter: Record<string, unknown> = {\n authors,\n kinds,\n limit,\n };\n\n wsRef.send(JSON.stringify(['REQ', subscriptionId, filter]));\n };\n\n wsRef.onmessage = (msg) => {\n try {\n const data = JSON.parse(msg.data);\n if (data[0] === 'EVENT') {\n const event = data[2] as NostrEvent;\n\n // If search term provided, filter by content\n if (search) {\n if (event.content.includes(search)) {\n events.push(event);\n }\n } else {\n events.push(event);\n }\n } else if (data[0] === 'EOSE') {\n cleanup();\n resolve();\n }\n } catch (err) {\n log.error({ relay: relayUrl, error: err }, 'Failed to parse event');\n }\n };\n\n wsRef.onerror = (err) => {\n log.error({ relay: relayUrl, error: err }, 'WebSocket error during query');\n cleanup();\n reject(err);\n };\n\n wsRef.onclose = () => {\n clearTimeout(timeout);\n };\n });\n } catch (err) {\n log.error({ relay: relayUrl, error: err }, 'Failed to query relay');\n } finally {\n if (ws && ws.readyState !== WebSocket.CLOSED) {\n ws.close();\n }\n }\n });\n\n await Promise.allSettled(queryPromises);\n\n log.info({ eventCount: events.length }, 'Query complete');\n\n return events;\n}\n\n/**\n * Verify a Nostr event's schnorr signature per NIP-01:\n *\n * id = sha256(JSON.stringify([0, pubkey, created_at, kind, tags, content]))\n * sig = schnorr_sign(id, secret_key)\n * check = schnorr_verify(id, pubkey, sig)\n *\n * Fails closed on any shape problem, decoding error, or mismatch. A true\n * return from this function is the ONLY thing that should be treated as\n * proof the event really came from the claimed pubkey.\n */\nasync function verifyNostrEventSignature(event: NostrEvent): Promise<boolean> {\n try {\n if (\n !event.id ||\n !event.sig ||\n !event.pubkey ||\n typeof event.id !== 'string' ||\n typeof event.sig !== 'string' ||\n typeof event.pubkey !== 'string' ||\n event.sig.length !== 128 ||\n !HEX_64_RE.test(event.id) ||\n !HEX_64_RE.test(event.pubkey)\n ) {\n return false;\n }\n\n // Recompute the event id from the canonical NIP-01 preimage. If the\n // relay (or an attacker) altered any field since signing, id won't\n // match and we reject.\n const preimage = JSON.stringify([\n 0,\n event.pubkey,\n event.created_at,\n event.kind,\n event.tags ?? [],\n event.content ?? '',\n ]);\n const computedId = toHex(sha256(new TextEncoder().encode(preimage)));\n if (computedId !== event.id.toLowerCase()) {\n log.warn(\n { eventId: event.id, computedId },\n 'nostr event id does not match its content'\n );\n return false;\n }\n\n return schnorr.verify(fromHex(event.sig), fromHex(event.id), fromHex(event.pubkey));\n } catch (err) {\n log.warn({ err }, 'schnorr verify threw');\n return false;\n }\n}\n\n/**\n * Verify that a user controls a Nostr identity\n *\n * Verification method:\n * 1. Query Nostr relays for kind 1 (text note) events from the claimed npub\n * 2. Check if any event content contains the attestation ID\n * 3. Verify the event signature\n *\n * @param attestationId - The attestation ID to look for\n * @param npub - The Nostr public key (npub format or hex)\n * @param relays - Optional list of relays to query\n * @returns Verification result with proof event if verified\n */\nexport async function verifyNostrIdentity(\n attestationId: string,\n npub: string,\n relays?: string[]\n): Promise<NostrVerificationResult> {\n log.info({ attestationId, npub }, 'Verifying Nostr identity');\n\n try {\n // Convert npub to hex if needed\n const pubkeyHex = npubToHex(npub);\n\n // Query Nostr for kind 1 events from this pubkey containing attestation ID\n const events = await queryNostrEvents({\n authors: [pubkeyHex],\n kinds: [1], // Text notes\n search: attestationId,\n relays,\n });\n\n if (events.length === 0) {\n log.info({ npub }, 'No verification events found');\n return {\n verified: false,\n error: 'No verification event found. User must post a note containing the attestation ID.',\n };\n }\n\n // Check each event for attestation ID and verify signature\n for (const event of events) {\n if (event.content.includes(attestationId)) {\n // Verify event signature\n const isValid = await verifyNostrEventSignature(event);\n\n if (isValid) {\n log.info({ npub, eventId: event.id }, 'Nostr identity verified');\n return {\n verified: true,\n event,\n };\n } else {\n log.warn({ npub, eventId: event.id }, 'Invalid event signature');\n }\n }\n }\n\n return {\n verified: false,\n error: 'Found events but none had valid signatures',\n };\n } catch (err) {\n log.error({ error: err, npub }, 'Failed to verify Nostr identity');\n return {\n verified: false,\n error: err instanceof Error ? err.message : 'Unknown error',\n };\n }\n}\n\n/**\n * Get verification instructions for Nostr identity\n */\nexport function getNostrVerificationInstructions(attestationId: string): string {\n return `To verify your Nostr identity:\n\n1. Open your Nostr client (Damus, Amethyst, Primal, etc.)\n2. Post a note containing this text:\n\n Verifying my OrangeCheck attestation: ${attestationId}\n\n3. Wait a few seconds for the note to propagate to relays\n4. Return here and click \"Verify\" to check\n\nThe verification will search Nostr relays for your note and confirm you control this identity.`;\n}\n\n/**\n * Check if a Nostr identity is already verified\n * This is a quick check that doesn't re-query relays\n */\nexport function isNostrIdentityVerified(\n attestationId: string,\n npub: string,\n cachedEvents: NostrEvent[]\n): boolean {\n const pubkeyHex = npubToHex(npub);\n\n return cachedEvents.some(\n (event) =>\n event.pubkey === pubkeyHex && event.kind === 1 && event.content.includes(attestationId)\n );\n}\n","/**\n * Twitter Identity Verification\n * Verifies that a user controls a Twitter identity\n *\n * Note: Twitter API requires authentication and has rate limits.\n * This implementation uses manual verification where the user provides a tweet URL.\n */\n\nimport { createLogger } from '../utils/logger';\n\nconst log = createLogger('identity-verification/twitter');\n\n/**\n * Verification result for Twitter identity\n */\nexport interface TwitterVerificationResult {\n verified: boolean;\n tweetUrl?: string;\n tweetId?: string;\n error?: string;\n}\n\n/**\n * Verify Twitter identity via manual tweet URL\n *\n * @param attestationId - The attestation ID to look for\n * @param handle - The Twitter handle (with or without @)\n * @param tweetUrl - Optional tweet URL provided by user\n * @returns Verification result\n */\nexport async function verifyTwitterIdentity(\n attestationId: string,\n handle: string,\n tweetUrl?: string\n): Promise<TwitterVerificationResult> {\n log.info({ attestationId, handle, tweetUrl }, 'Verifying Twitter identity');\n\n if (!tweetUrl) {\n return {\n verified: false,\n error: 'Tweet URL required for verification',\n };\n }\n\n try {\n // Extract tweet ID from URL\n const tweetId = extractTweetId(tweetUrl);\n\n if (!tweetId) {\n return {\n verified: false,\n error: 'Invalid tweet URL format',\n };\n }\n\n // For now, we can't verify without Twitter API access\n // Return instructions for manual verification\n log.info({ handle, tweetId }, 'Manual verification required');\n\n return {\n verified: false,\n tweetUrl,\n tweetId,\n error: 'Automatic verification not available. Please verify manually by checking the tweet.',\n };\n } catch (err) {\n log.error({ error: err, handle }, 'Failed to verify Twitter identity');\n return {\n verified: false,\n error: err instanceof Error ? err.message : 'Unknown error',\n };\n }\n}\n\n/**\n * Extract tweet ID from various Twitter URL formats\n */\nfunction extractTweetId(url: string): string | null {\n // Support various Twitter URL formats:\n // https://twitter.com/username/status/1234567890\n // https://x.com/username/status/1234567890\n // https://mobile.twitter.com/username/status/1234567890\n\n const patterns = [\n /(?:twitter\\.com|x\\.com)\\/\\w+\\/status\\/(\\d+)/,\n /(?:mobile\\.twitter\\.com)\\/\\w+\\/status\\/(\\d+)/,\n ];\n\n for (const pattern of patterns) {\n const match = url.match(pattern);\n if (match && match[1]) {\n return match[1];\n }\n }\n\n return null;\n}\n\n/**\n * Get verification instructions for Twitter identity\n */\nexport function getTwitterVerificationInstructions(attestationId: string, handle: string): string {\n const cleanHandle = handle.startsWith('@') ? handle.slice(1) : handle;\n\n return `To verify your Twitter identity (@${cleanHandle}):\n\n1. Go to https://twitter.com/compose/tweet\n2. Post a tweet with this text:\n\n Verifying my OrangeCheck attestation: ${attestationId}\n\n3. After posting, copy the tweet URL\n4. Paste the URL below and click \"Verify\"\n\nExample tweet URL:\nhttps://twitter.com/${cleanHandle}/status/1234567890\n\nThe verification will check that the tweet exists and contains the attestation ID.`;\n}\n\n/**\n * Generate tweet text for verification\n */\nexport function generateTweetText(attestationId: string): string {\n return `Verifying my OrangeCheck attestation: ${attestationId}\n\nProving Bitcoin reputation with cryptographic signatures.\n\n#OrangeCheck #Bitcoin`;\n}\n\n/**\n * Get direct link to compose a verification tweet\n */\nexport function getTwitterComposeUrl(attestationId: string): string {\n const text = generateTweetText(attestationId);\n const encoded = encodeURIComponent(text);\n\n return `https://twitter.com/intent/tweet?text=${encoded}`;\n}\n\n/**\n * Validate tweet URL format\n */\nexport function isValidTweetUrl(url: string): boolean {\n return extractTweetId(url) !== null;\n}\n\n/**\n * Get tweet URL from handle and tweet ID\n */\nexport function getTweetUrl(handle: string, tweetId: string): string {\n const cleanHandle = handle.startsWith('@') ? handle.slice(1) : handle;\n return `https://twitter.com/${cleanHandle}/status/${tweetId}`;\n}\n\n/**\n * Manual verification helper\n * Returns instructions for verifier to manually check the tweet\n */\nexport function getManualVerificationInstructions(\n attestationId: string,\n handle: string,\n tweetUrl: string\n): string {\n return `Manual Verification Steps:\n\n1. Open this tweet: ${tweetUrl}\n2. Verify it's posted by @${handle}\n3. Check that the tweet contains: ${attestationId}\n4. Confirm the tweet is public and not deleted\n\nIf all checks pass, the identity is verified.`;\n}\n","import type { IdentityBinding } from '../types';\nimport type { DnsVerificationResult } from './dns';\nimport type { GitHubVerificationResult } from './github';\nimport type { NostrVerificationResult } from './nostr';\nimport type { TwitterVerificationResult } from './twitter';\n\nimport { logVerificationError } from '../utils/logger';\nimport { enforceRateLimit, identityVerificationLimiter } from '../utils/rate-limiter';\nimport { getDnsVerificationInstructions, verifyDnsIdentity } from './dns';\nimport { getGitHubVerificationInstructions, verifyGitHubIdentity } from './github';\nimport { getNostrVerificationInstructions, verifyNostrIdentity } from './nostr';\nimport { getTwitterVerificationInstructions, verifyTwitterIdentity } from './twitter';\n\n/**\n * Identity Verification\n *\n * Unified API for verifying identity bindings across multiple protocols\n */\n\nexport * from './nostr';\nexport * from './github';\nexport * from './twitter';\nexport * from './dns';\n\n/**\n * Unified verification result across all protocols\n */\nexport type VerificationResult =\n | NostrVerificationResult\n | GitHubVerificationResult\n | TwitterVerificationResult\n | DnsVerificationResult;\n\n/**\n * Options for identity verification\n */\nexport interface VerifyIdentityOptions {\n /** Tweet URL for Twitter verification */\n tweetUrl?: string;\n /** Relay URLs for Nostr verification */\n relays?: string[];\n}\n\n/**\n * Verify an identity binding\n *\n * @param attestationId - The attestation ID to verify against\n * @param identity - The identity binding to verify\n * @param options - Protocol-specific verification options\n * @returns Verification result with verified status and optional error\n *\n * @example\n * ```typescript\n * const result = await verifyIdentity(\n * 'abc123...',\n * { protocol: 'nostr', identifier: 'npub1...' },\n * { relays: ['wss://relay.damus.io'] }\n * );\n *\n * if (result.verified) {\n * console.log('Identity verified!');\n * }\n * ```\n */\nexport async function verifyIdentity(\n attestationId: string,\n identity: IdentityBinding,\n options?: VerifyIdentityOptions\n): Promise<VerificationResult> {\n const { protocol, identifier } = identity;\n\n // Rate limiting: 5 requests per minute per identity\n const rateLimitKey = `${protocol}:${identifier}`;\n try {\n enforceRateLimit(identityVerificationLimiter, rateLimitKey);\n } catch (err) {\n return {\n verified: false,\n error: err instanceof Error ? err.message : 'Rate limit exceeded',\n };\n }\n\n try {\n switch (protocol) {\n case 'nostr':\n return await verifyNostrIdentity(attestationId, identifier, options?.relays);\n\n case 'github':\n return await verifyGitHubIdentity(attestationId, identifier);\n\n case 'twitter':\n return await verifyTwitterIdentity(attestationId, identifier, options?.tweetUrl);\n\n case 'dns':\n return await verifyDnsIdentity(attestationId, identifier);\n\n case 'email':\n // Email verification not implemented yet\n return {\n verified: false,\n error: 'Email verification not implemented',\n };\n\n case 'web':\n // Web verification similar to DNS\n return await verifyDnsIdentity(attestationId, identifier);\n\n case 'did':\n // DID verification not implemented yet\n return {\n verified: false,\n error: 'DID verification not implemented',\n };\n\n default:\n return {\n verified: false,\n error: `Unknown protocol: ${protocol}`,\n };\n }\n } catch (error) {\n // Log error\n logVerificationError(\n protocol,\n identifier,\n error instanceof Error ? error : new Error(String(error)),\n {\n attestationId,\n }\n );\n\n return {\n verified: false,\n error: error instanceof Error ? error.message : 'Verification failed',\n };\n }\n}\n\n/**\n * Get verification instructions for an identity\n */\nexport function getVerificationInstructions(\n attestationId: string,\n identity: IdentityBinding\n): string {\n const { protocol, identifier } = identity;\n\n switch (protocol) {\n case 'nostr':\n return getNostrVerificationInstructions(attestationId);\n\n case 'github':\n return getGitHubVerificationInstructions(attestationId);\n\n case 'twitter':\n return getTwitterVerificationInstructions(attestationId, identifier);\n\n case 'dns':\n return getDnsVerificationInstructions(attestationId, identifier);\n\n case 'email':\n return `Email verification not implemented yet.`;\n\n case 'web':\n return getDnsVerificationInstructions(attestationId, identifier);\n\n case 'did':\n return `DID verification not implemented yet.`;\n\n default:\n return `Unknown protocol: ${protocol}`;\n }\n}\n\n/**\n * Check if a protocol supports automatic verification\n */\nexport function supportsAutomaticVerification(protocol: string): boolean {\n return ['nostr', 'github', 'dns', 'web'].includes(protocol);\n}\n\n/**\n * Check if a protocol requires manual verification\n */\nexport function requiresManualVerification(protocol: string): boolean {\n return ['twitter', 'email'].includes(protocol);\n}\n\n/**\n * Get verification status for multiple identities\n */\nexport async function verifyAllIdentities(\n attestationId: string,\n identities: IdentityBinding[],\n options?: {\n tweetUrls?: Record<string, string>; // Map of identifier -> tweet URL\n relays?: string[];\n }\n): Promise<Map<string, VerificationResult>> {\n const results = new Map<string, VerificationResult>();\n\n await Promise.all(\n identities.map(async (identity) => {\n const key = `${identity.protocol}:${identity.identifier}`;\n const tweetUrl = options?.tweetUrls?.[identity.identifier];\n\n const result = await verifyIdentity(attestationId, identity, {\n tweetUrl,\n relays: options?.relays,\n });\n\n results.set(key, result);\n })\n );\n\n return results;\n}\n\n/**\n * Get summary of verification results\n */\nexport function getVerificationSummary(results: Map<string, VerificationResult>): {\n total: number;\n verified: number;\n failed: number;\n pending: number;\n} {\n let verified = 0;\n let failed = 0;\n let pending = 0;\n\n for (const result of results.values()) {\n if (result.verified) {\n verified++;\n } else if (result.error?.includes('not implemented')) {\n pending++;\n } else {\n failed++;\n }\n }\n\n return {\n total: results.size,\n verified,\n failed,\n pending,\n };\n}\n"]}