diff --git a/lite/README.md b/lite/README.md new file mode 100644 index 0000000..30a070b --- /dev/null +++ b/lite/README.md @@ -0,0 +1,260 @@ +# NOSTR_LOGIN_LITE + +A minimal, dependency-light replacement for the current auth/UI stack that preserves all login methods and window.nostr surface. + +## Features + +- ✅ Single file distributable (`nostr-login-lite.bundle.js`) +- ✅ All major login methods: Extension, Local key, Read-only +- ✅ Compatible window.nostr facade +- ✅ NIP-04 and NIP-44 encryption (local) +- ✅ Minimal vanilla JavaScript modal +- ✅ No bulky frameworks (no Stencil/Tailwind) +- ✅ Just ~50KB gzipped (minus nostr-tools) + +## Installation + +```html + + + + + +``` + +## Quick Start + +```javascript +// Initialize with default options +await window.NOSTR_LOGIN_LITE.init({ + theme: 'light', + relays: ['wss://relay.damus.io'], + methods: { + extension: true, + local: true, + readonly: true + } +}); + +// Launch login modal +window.NOSTR_LOGIN_LITE.launch(); +``` + +## API Reference + +### Initialization +```javascript +// Initialize the library +await window.NOSTR_LOGIN_LITE.init(options) + +// Options +{ + theme: 'light' | 'dark', + darkMode: boolean, + relays: string[], // Default relays for NIP-46 + methods: { + connect: boolean, // NIP-46 providers (coming) + extension: boolean, // Browser extensions + local: boolean, // Local key generation + readonly: boolean, // Read-only mode + otp: boolean // OTP/DM (coming) + } +} +``` + +### User Interface +```javascript +// Launch authentication modal +window.NOSTR_LOGIN_LITE.launch(startScreen); + +// Available screens +'login' // Auth selection +'signup' // Account creation +'switch' // Account switching + +// Logout current user +window.NOSTR_LOGIN_LITE.logout(); + +// Set theme +window.NOSTR_LOGIN_LITE.setDarkMode(dark: boolean); +``` + +### Programmatic Auth +```javascript +// Set authentication manually +window.NOSTR_LOGIN_LITE.setAuth({ + type: 'login' | 'signup' | 'logout', + method: 'extension' | 'local' | 'readonly' | 'connect' | 'otp', + pubkey: string, // Public key + secret: string // Private key (optional) +}); + +// Cancel ongoing auth flow +window.NOSTR_LOGIN_LITE.cancelNeedAuth(); +``` + +### window.nostr Compatibility + +All standard window.nostr methods work transparently: + +```javascript +// Get current user's public key +const pubkey = await window.nostr.getPublicKey(); + +// Sign an event +const signed = await window.nostr.signEvent({ + kind: 1, + content: 'Hello Nostr!', + tags: [], + created_at: Date.now() +}); + +// NIP-04 encrypt/decrypt +const encrypted = await window.nostr.nip04.encrypt(recipientPubkey, 'secret'); +const decrypted = await window.nostr.nip04.decrypt(senderPubkey, encrypted); + +// NIP-44 encrypt/decrypt (if available in nostr-tools) +const encrypted = await window.nostr.nip44.encrypt(recipientPubkey, 'secret'); +const decrypted = await window.nostr.nip44.decrypt(senderPubkey, encrypted); +``` + +## Events + +Listen for authentication events: + +```javascript +// Auth state changes +window.addEventListener('nlAuth', (event) => { + console.log(event.detail); + // { type: 'login', pubkey: '...', method: 'local' } +}); + +// Dark mode changes +window.addEventListener('nlDarkMode', (event) => { + document.body.classList.toggle('dark', event.detail.dark); +}); + +// Auth URLs (for NIP-46) +window.addEventListener('nlAuthUrl', (event) => { + console.log('Auth URL:', event.detail.url); +}); + +// Logout events +window.addEventListener('nlLogout', () => { + console.log('User logged out'); +}); +``` + +## Architecture + +``` +NOSTR_LOGIN_LITE/ +├── nostr-login-lite.bundle.js # Single distributable +├── core/ +│ ├── nip46-client.js # NIP-46 transport over websockets +│ └── {other components} # Future components +├── ui/ +│ └── modal.js # Vanilla JS modal +└── README.md # This file +``` + +### Key Components + +- **Modal**: Lightweight modal using vanilla CSS +- **Store**: localStorage helpers for accounts/relays +- **LocalSigner**: Wraps nostr-tools for local signing +- **ExtensionBridge**: Detects and bridges browser extensions +- **NIP46Client**: Handles remote signing (NIP-46) +- **Relays**: Default relay management + +## Browser Support + +- Modern browsers with ES2018+ support +- WebWorkers (for NIP-46) +- localStorage (for account storage) + +## Dependencies + +This project uses a local copy of nostr-tools instead of loading from CDN: + +- **nostr.bundle.js**: Local nostr-tools bundle (~200KB) +- **nostr-login-lite.bundle.js**: This library (~50KB gzipped) + +## Size Comparison + +``` +nostr-tools ~200KB (served locally) +NOSTR_LOGIN_LITE ~50KB (gzipped) +Total: ~250KB + +vs. + +Full nostr-login: ~2.5MB+ framework dependencies +``` + +## Future Roadmap ⚠️ + +The following features are planned but not yet implemented: + +- **NIP-46 Connect**: External signer integration +- **OTP/DM**: Server-side OTP delivery +- **Connect Wallet**: Wallet integration +- **Advanced UI**: Multi-screen flows +- **NIP-05 Profiles**: User discovery +- **Backup/Restore**: Key backup functionality + +## Development + +To work on the source files: + +```bash +# Edit individual components +lite/core/nip46-client.js +lite/ui/modal.js +lite/nostr-login-lite.js + +# Run bundler to create distribution +node lite/bundler.js + +# Start dev server (from project root) +python3 -m http.server 8000 + +# Open test page +open http://localhost:8000/examples/simple-demo.html +``` + +### Local Bundle Setup + +The project uses a local copy of nostr-tools: +- `lite/nostr.bundle.js` - Local nostr-tools bundle (serves from this location) +- `nostr-tools/` - Reference directory (excluded from git, not used in runtime) +- Examples load from: `../lite/nostr.bundle.js` + +## Examples + +See `examples/` directory: +- `simple-demo.html` - Basic functionality demo +- `full-test.html` - Comprehensive test suite +- `test-lite.html` - Minimal functionality test + +## Migration from Full nostr-login + +The lite version provides the same API surface, but is much smaller: + +```javascript +// Before (full library) +import { init, launch, logout } from 'nostr-login'; + +// After (lite version) +await window.NOSTR_LOGIN_LITE.init(); +window.NOSTR_LOGIN_LITE.launch(); +window.NOSTR_LOGIN_LITE.logout(); +``` + +## License + +Same license as the original nostr-login project. + +## Contributing + +This is meant to be lightweight and focused. Contributions should maintain the minimal footprint while extending functionality. \ No newline at end of file diff --git a/lite/bundler-clean.js b/lite/bundler-clean.js new file mode 100644 index 0000000..b7388c9 --- /dev/null +++ b/lite/bundler-clean.js @@ -0,0 +1,97 @@ +/** + * Clean bundler for NOSTR_LOGIN_LITE + * Removes problematic files and recreates bundle + */ + +const fs = require('fs'); +const path = require('path'); + +async function createCleanBundle() { + // First, remove the old bundle if it exists + const outputPath = path.join(__dirname, 'nostr-login-lite.bundle.js'); + try { + if (fs.existsSync(outputPath)) { + fs.unlinkSync(outputPath); + } + } catch (e) { + console.log('No old bundle to remove'); + } + + const mainFile = path.join(__dirname, 'nostr-login-lite.js'); + const nip46File = path.join(__dirname, 'core/nip46-client.js'); + const modalFile = path.join(__dirname, 'ui/modal.js'); + + // Start with a clean header + let bundle = `/** + * NOSTR_LOGIN_LITE + * Single-file Nostr authentication library + * Generated on: ${new Date().toISOString()} + */ + +`; + + // Add section markers and combine files + const files = [ + { path: modalFile, name: 'modal.js' }, + { path: nip46File, name: 'nip46-client.js' }, + { path: mainFile, name: 'nostr-login-lite.js' } + ]; + + for (const file of files) { + if (fs.existsSync(file.path)) { + const content = fs.readFileSync(file.path, 'utf8'); + + bundle += `\n// ======================================\n`; + bundle += `// ${file.name}\n`; + bundle += `// ======================================\n\n`; + + // Clean the content by removing initial header comments + let lines = content.split('\n'); + let contentStartIndex = 0; + + // Skip the first 10 lines if they contain file headers + for (let i = 0; i < Math.min(10, lines.length); i++) { + const line = lines[i].trim(); + if (line.startsWith('/**') || line.startsWith('*') || line.startsWith('/*') || line.startsWith('//') || + line.includes('Copyright') || line.includes('@license') || line.includes('Licensed') || line.includes('©')) { + contentStartIndex = i + 1; + } + } + + if (contentStartIndex > 0) { + lines = lines.slice(contentStartIndex); + } + + bundle += lines.join('\n'); + bundle += '\n\n'; + + console.log(`Added ${file.name}`); + } else { + console.warn(`File not found: ${file.path}`); + } + } + + // Write the bundled file + fs.writeFileSync(outputPath, bundle, 'utf8'); + + const sizeKB = (bundle.length / 1024).toFixed(2); + console.log(`\n✅ Clean bundle created: ${outputPath}`); + console.log(`📏 Bundle size: ${sizeKB} KB`); + console.log(`📄 Total lines: ${bundle.split('\n').length}`); + + // Verify the bundle starts correctly + const firstLines = bundle.split('\n').slice(0, 20).join('\n'); + console.log('\n📋 First 20 lines:'); + console.log(firstLines); + + return bundle; +} + +if (typeof module !== 'undefined' && module.exports) { + module.exports = { createCleanBundle }; +} + +// Run if called directly +if (typeof require !== 'undefined' && require.main === module) { + createCleanBundle().catch(console.error); +} \ No newline at end of file diff --git a/lite/bundler.js b/lite/bundler.js new file mode 100644 index 0000000..8069c05 --- /dev/null +++ b/lite/bundler.js @@ -0,0 +1,86 @@ +/** + * Simple bundler for NOSTR_LOGIN_LITE + * Combines all files into a single distributable script + */ + +const fs = require('fs'); +const path = require('path'); + +async function bundleLite() { + const mainFile = path.join(__dirname, 'nostr-login-lite.js'); + const nip46File = path.join(__dirname, 'core/nip46-client.js'); + const modalFile = path.join(__dirname, 'ui/modal.js'); + + let bundle = `/** + * NOSTR_LOGIN_LITE + * Single-file Nostr authentication library + * Generated on: ${new Date().toISOString()} + */ + +// ====================================== +// Core Classes and Components +// ====================================== +`; + + // Read and combine files + const files = [modalFile, nip46File, mainFile]; + + for (const file of files) { + if (fs.existsSync(file)) { + let content = fs.readFileSync(file, 'utf8'); + + // Skip the initial comment and license if present + let lines = content.split('\n'); + + // Find and skip complete JSDoc blocks at the beginning + let skipUntil = 0; + let inJSDocBlock = false; + for (let i = 0; i < lines.length; i++) { + const line = lines[i].trim(); + + if (line.startsWith('/**')) { + inJSDocBlock = true; + skipUntil = i; + } else if (inJSDocBlock && line.startsWith('*/')) { + skipUntil = i; + break; + } else if (i < 10 && (line.startsWith('const') || line.startsWith('class') || line.startsWith('function'))) { + // Hit actual code before finding end of JSDoc block + inJSDocBlock = false; + break; + } + } + + if (inJSDocBlock) { + lines = lines.slice(skipUntil + 1); // Skip the entire JSDoc block + } else { + // Fallback to old filtering (skip comment-like lines in first 10) + lines = lines.filter((line, index) => { + return index >= 10 || !line.trim().startsWith('*') && !line.trim().startsWith('//'); + }); + } + + bundle += '\n// ======================================\n'; + bundle += `// ${path.basename(file)}\n`; + bundle += '// ======================================\n\n'; + bundle += lines.join('\n'); + bundle += '\n\n'; + } + } + + // Write the bundled file + const outputPath = path.join(__dirname, 'nostr-login-lite.bundle.js'); + fs.writeFileSync(outputPath, bundle); + + console.log('Bundle created:', outputPath); + console.log('Bundle size:', (bundle.length / 1024).toFixed(2), 'KB'); +} + +if (typeof module !== 'undefined' && module.exports) { + module.exports = { bundleLite }; +} + +// Run if called directly +if (typeof require !== 'undefined' && require.main === module) { + bundleLite().catch(console.error); +} \ No newline at end of file diff --git a/lite/core/nip46-client.js b/lite/core/nip46-client.js new file mode 100644 index 0000000..208f1c0 --- /dev/null +++ b/lite/core/nip46-client.js @@ -0,0 +1,398 @@ +/** + * NOSTR NIP-46 Client Implementation + * Minimal RPC over NostrTools.SimplePool for NOSTR_LOGIN_LITE + */ + +class NIP46Client { + constructor() { + this.pool = null; + this.localSk = null; + this.localPk = null; + this.remotePk = null; + this.relays = []; + this.sub = null; + this.pendingRequests = {}; + this.useNip44 = false; + this.iframeOrigin = null; + this.iframePort = null; + } + + init(localSk, remotePk, relays, iframeOrigin) { + // Create SimplePool + this.pool = new window.NostrTools.SimplePool(); + + // Setup keys + this.localSk = localSk; + if (this.localSk) { + this.localPk = window.NostrTools.getPublicKey(this.localSk); + } + + this.remotePk = remotePk; + this.relays = [...relays]; + + // Store iframe origin for future use + this.iframeOrigin = iframeOrigin; + + console.log('NIP46Client initialized for', this.remotePk ? 'remote signer' : 'listening mode'); + } + + setUseNip44(use) { + this.useNip44 = use; + } + + subscribeReplies() { + if (!this.pool || !this.localPk) return; + + // Subscribe to replies to our pubkey on kind 24133 (NIP-46 methods) + this.sub = this.pool.sub(this.relays, [{ + kinds: [24133], + '#p': [this.localPk] + }]); + + this.sub.on('event', (event) => this.onEvent(event)); + this.sub.on('eose', () => { + console.log('NIP-46 subscription caught up'); + }); + + console.log('Subscribed to NIP-46 replies on relays:', this.relays); + } + + unsubscribe() { + if (this.sub) { + this.sub.unsub(); + this.sub = null; + } + } + + async onEvent(event) { + console.log('NIP-46 event received:', event); + + try { + const parsed = await this.parseEvent(event); + if (parsed) { + if (parsed.id && this.pendingRequests[parsed.id]) { + // Handle response + const handler = this.pendingRequests[parsed.id]; + delete this.pendingRequests[parsed.id]; + + if (parsed.result !== undefined) { + handler.resolve(parsed.result); + } else if (parsed.error) { + handler.reject(new Error(parsed.error)); + } else { + handler.reject(new Error('Invalid response format')); + } + } else if (parsed.method === 'auth_url') { + // Handle auth_url emissions (deduplication required) + this.emitAuthUrlIfNeeded(parsed.params[0]); + } + } + } catch (error) { + console.error('Error processing NIP-46 event:', error); + } + } + + emitAuthUrlIfNeeded(url) { + // Deduplicate auth_url emissions - only emit if not recently shown + const lastUrl = sessionStorage.getItem('nl-last-auth-url'); + if (lastUrl === url) { + console.log('Auth URL already shown, skipping duplicate:', url); + return; + } + + sessionStorage.setItem('nl-last-auth-url', url); + console.log('New auth URL:', url); + + // Emit event for UI + window.dispatchEvent(new CustomEvent('nlAuthUrl', { detail: { url } })); + } + + async parseEvent(event) { + try { + let content = event.content; + + // Determine encryption method based on content structure + if (content.length > 44) { + // Likely NIP-44 (encrypted) + if (this.localSk && event.pubkey) { + try { + content = window.NostrTools.nip44?.decrypt(this.localSk, event.pubkey, content); + } catch (e) { + console.warn('NIP-44 decryption failed, trying NIP-04...'); + content = await window.NostrTools.nip04.decrypt(this.localSk, event.pubkey, content); + } + } + } else { + // Likely NIP-04 + if (this.localSk && event.pubkey) { + content = await window.NostrTools.nip04.decrypt(this.localSk, event.pubkey, content); + } + } + + const payload = JSON.parse(content); + console.log('Decrypted NIP-46 payload:', payload); + + return { + id: payload.id, + method: payload.method, + params: payload.params, + result: payload.result, + error: payload.error, + event: event + }; + + } catch (e) { + console.error('Failed to parse event:', e); + return null; + } + } + + async listen(nostrConnectSecret) { + return new Promise((resolve, reject) => { + if (!this.localPk) { + reject(new Error('No local pubkey available for listening')); + return; + } + + // Subscribe to unsolicited events to our pubkey + let foundSecretOrAck = false; + + const listenSub = this.pool.sub(this.relays, [{ + kinds: [24133], + '#p': [this.localPk] + }]); + + listenSub.on('event', async (event) => { + try { + const parsed = await this.parseEvent(event); + if (parsed && parsed.method === 'connect') { + // Accept if it's an ack or matches our secret + const [userPubkey, token] = parsed.params || []; + + if (token === '' && parsed.result === 'ack') { + // Ack received + foundSecretOrAck = true; + listenSub.unsub(); + resolve(event.pubkey); + } else if (token === nostrConnectSecret) { + // Secret match + foundSecretOrAck = true; + listenSub.unsub(); + resolve(event.pubkey); + } + } + } catch (error) { + console.error('Error in listen mode:', error); + } + }); + + // Timeout after 5 minutes + setTimeout(() => { + if (!foundSecretOrAck) { + listenSub.unsub(); + reject(new Error('Listen timeout - no signer connected')); + } + }, 300000); + }); + } + + async connect(token, perms) { + return new Promise(async (resolve, reject) => { + try { + const result = await this.sendRequest( + this.remotePk, + 'connect', + [this.localPk, token || '', perms || ''], + 24133, + (response) => { + if (response === 'ack') { + resolve(true); + } else { + reject(new Error('Connection not acknowledged')); + } + } + ); + + // Set 30 second timeout + setTimeout(() => reject(new Error('Connection timeout')), 30000); + + } catch (error) { + reject(error); + } + }); + } + + async initUserPubkey(hint) { + if (hint) { + this.remotePk = hint; + return hint; + } + + if (!this.remotePk) { + // Request get_public_key + return new Promise(async (resolve, reject) => { + try { + const pubkey = await this.sendRequest( + this.remotePk, + 'get_public_key', + [], + 24133 + ); + this.remotePk = pubkey; + resolve(pubkey); + } catch (error) { + reject(error); + } + }); + } + + return this.remotePk; + } + + async sendRequest(remotePubkey, method, params, kind = 24133, cb) { + if (!this.pool || !this.localSk || !this.localPk) { + throw new Error('NIP46Client not properly initialized'); + } + + if (!remotePubkey) { + throw new Error('No remote pubkey specified'); + } + + const id = this._generateId(); + + // Create request event + const event = await this.createRequestEvent(id, remotePubkey, method, params, kind); + + console.log('Sending NIP-46 request:', { id, method, params }); + + // Publish to relays + const pubs = await this.pool.publish(this.relays, event); + console.log('Published to relays, waiting for response...'); + + return new Promise((resolve, reject) => { + // Set timeout + const timeout = setTimeout(() => { + console.error('NIP-46 request timeout for id:', id); + delete this.pendingRequests[id]; + reject(new Error(`Request timeout for ${method}`)); + }, 60000); // 1 minute timeout + + // Store handler + this.pendingRequests[id] = { + resolve: (result) => { + clearTimeout(timeout); + resolve(result); + }, + reject: (error) => { + clearTimeout(timeout); + reject(error); + }, + timestamp: Date.now() + }; + + // If callback provided, override resolve + if (cb) { + const originalResolve = this.pendingRequests[id].resolve; + this.pendingRequests[id].resolve = (result) => { + cb(result); + originalResolve(result); + }; + } + }); + } + + async createRequestEvent(id, remotePubkey, method, params, kind = 24133) { + let content = JSON.stringify({ + id, + method, + params + }); + + // Choose encryption method + let encrypted = content; + if (method !== 'create_account') { + // Use NIP-44 for non-account creation methods if available + if (this.useNip44 && window.NostrTools.nip44) { + encrypted = window.NostrTools.nip44.encrypt(this.localSk, remotePubkey, content); + } else { + // Fallback to NIP-04 + encrypted = await window.NostrTools.nip04.encrypt(this.localSk, remotePubkey, content); + } + } + + // Create event structure + const event = { + kind: kind, + content: encrypted, + tags: [ + ['p', remotePubkey] + ], + created_at: Math.floor(Date.now() / 1000), + pubkey: this.localPk, + id: '', // Will be set by finalizeEvent + sig: '' // Will be set by finalizeEvent + }; + + // Sign the event + const signedEvent = window.NostrTools.finalizeEvent(event, this.localSk); + + return signedEvent; + } + + _generateId() { + return 'nl-' + Date.now() + '-' + Math.random().toString(36).substring(2, 15); + } + + setWorkerIframePort(port) { + this.iframePort = port; + + // Set up postMessage routing if needed + if (this.iframePort && this.iframeOrigin) { + this.iframePort.onmessage = (event) => { + if (event.origin !== this.iframeOrigin) { + console.warn('Ignoring message from unknown origin:', event.origin); + return; + } + + console.log('Received iframe message:', event.data); + // Handle iframe messages + }; + + // Send keepalive + setInterval(() => { + if (this.iframePort) { + try { + this.iframePort.postMessage({ type: 'ping' }); + } catch (e) { + console.warn('Iframe port closed'); + this.iframePort = null; + } + } + }, 30000); // 30 seconds + } + } + + teardown() { + this.unsubscribe(); + + if (this.iframePort) { + try { + this.iframePort.close(); + } catch (e) { + console.warn('Error closing iframe port:', e); + } + this.iframePort = null; + } + + if (this.pool) { + this.pool.close(this.relays); + this.pool = null; + } + + // Clear all pending requests + for (const id in this.pendingRequests) { + this.pendingRequests[id].reject(new Error('Client teardown')); + } + this.pendingRequests = {}; + } +} \ No newline at end of file diff --git a/lite/nostr-login-lite.bundle.js b/lite/nostr-login-lite.bundle.js new file mode 100644 index 0000000..6f55a95 --- /dev/null +++ b/lite/nostr-login-lite.bundle.js @@ -0,0 +1,2339 @@ +/** + * NOSTR_LOGIN_LITE + * Single-file Nostr authentication library + * Generated on: 2025-09-13T13:03:05.960Z + */ + +// ====================================== +// Core Classes and Components +// ====================================== + +// ====================================== +// modal.js +// ====================================== + + +class Modal { + constructor(options) { + this.options = options; + this.container = null; + this.isVisible = false; + this.currentScreen = null; + + // Initialize modal container and styles + this._initModal(); + } + + _initModal() { + // Create modal container + this.container = document.createElement('div'); + this.container.id = 'nl-modal'; + this.container.style.cssText = ` + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.75); + display: none; + z-index: 10000; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + `; + + // Create modal content + const modalContent = document.createElement('div'); + modalContent.style.cssText = ` + position: relative; + background: white; + width: 90%; + max-width: 400px; + margin: 50px auto; + border-radius: 12px; + box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); + max-height: 600px; + overflow: hidden; + `; + + // Header + const modalHeader = document.createElement('div'); + modalHeader.style.cssText = ` + padding: 20px 24px 0 24px; + display: flex; + justify-content: space-between; + align-items: center; + `; + + const modalTitle = document.createElement('h2'); + modalTitle.textContent = 'Nostr Login'; + modalTitle.style.cssText = ` + margin: 0; + font-size: 24px; + font-weight: 600; + color: #1f2937; + `; + + const closeButton = document.createElement('button'); + closeButton.innerHTML = '×'; + closeButton.onclick = () => this.close(); + closeButton.style.cssText = ` + background: none; + border: none; + font-size: 28px; + color: #6b7280; + cursor: pointer; + padding: 0; + width: 32px; + height: 32px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 6px; + `; + closeButton.onmouseover = () => closeButton.style.background = '#f3f4f6'; + closeButton.onmouseout = () => closeButton.style.background = 'none'; + + modalHeader.appendChild(modalTitle); + modalHeader.appendChild(closeButton); + + // Body + this.modalBody = document.createElement('div'); + this.modalBody.style.cssText = ` + padding: 24px; + overflow-y: auto; + max-height: 500px; + `; + + modalContent.appendChild(modalHeader); + modalContent.appendChild(this.modalBody); + this.container.appendChild(modalContent); + + // Add to body + document.body.appendChild(this.container); + + // Click outside to close + this.container.onclick = (e) => { + if (e.target === this.container) { + this.close(); + } + }; + + // Update theme + this.updateTheme(); + } + + updateTheme() { + const isDark = this.options?.darkMode; + const modalContent = this.container.querySelector(':nth-child(1)'); + const title = this.container.querySelector('h2'); + + if (isDark) { + modalContent.style.background = '#1f2937'; + title.style.color = 'white'; + } else { + modalContent.style.background = 'white'; + title.style.color = '#1f2937'; + } + } + + open(opts = {}) { + this.currentScreen = opts.startScreen; + this.isVisible = true; + this.container.style.display = 'block'; + + // Render login options + this._renderLoginOptions(); + } + + close() { + this.isVisible = false; + this.container.style.display = 'none'; + this.modalBody.innerHTML = ''; + } + + _renderLoginOptions() { + this.modalBody.innerHTML = ''; + + const options = []; + + // Extension option + if (this.options?.methods?.extension !== false) { + options.push({ + type: 'extension', + title: 'Browser Extension', + description: 'Use your browser extension', + icon: '🔌' + }); + } + + // Local key option + if (this.options?.methods?.local !== false) { + options.push({ + type: 'local', + title: 'Local Key', + description: 'Create or import your own key', + icon: '🔑' + }); + } + + // Nostr Connect option + if (this.options?.methods?.connect !== false) { + options.push({ + type: 'connect', + title: 'Nostr Connect', + description: 'Connect with external signer', + icon: '🌐' + }); + } + + // Read-only option + if (this.options?.methods?.readonly !== false) { + options.push({ + type: 'readonly', + title: 'Read Only', + description: 'Browse without signing', + icon: '👁️' + }); + } + + // OTP/DM option + if (this.options?.methods?.otp !== false && this.options?.otp) { + options.push({ + type: 'otp', + title: 'DM/OTP', + description: 'Receive OTP via DM', + icon: '📱' + }); + } + + // Render each option + options.forEach(option => { + const button = document.createElement('button'); + button.onclick = () => this._handleOptionClick(option.type); + button.style.cssText = ` + display: flex; + align-items: center; + width: 100%; + padding: 16px; + margin-bottom: 12px; + background: ${this.options?.darkMode ? '#374151' : 'white'}; + border: 1px solid ${this.options?.darkMode ? '#4b5563' : '#d1d5db'}; + border-radius: 8px; + cursor: pointer; + transition: all 0.2s; + `; + button.onmouseover = () => { + button.style.boxShadow = '0 4px 6px -1px rgba(0, 0, 0, 0.1)'; + }; + button.onmouseout = () => { + button.style.boxShadow = 'none'; + }; + + const iconDiv = document.createElement('div'); + iconDiv.textContent = option.icon; + iconDiv.style.cssText = ` + font-size: 24px; + margin-right: 16px; + width: 24px; + text-align: center; + `; + + const contentDiv = document.createElement('div'); + contentDiv.style.cssText = 'flex: 1; text-align: left;'; + + const titleDiv = document.createElement('div'); + titleDiv.textContent = option.title; + titleDiv.style.cssText = ` + font-weight: 600; + margin-bottom: 4px; + color: ${this.options?.darkMode ? 'white' : '#1f2937'}; + `; + + const descDiv = document.createElement('div'); + descDiv.textContent = option.description; + descDiv.style.cssText = ` + font-size: 14px; + color: ${this.options?.darkMode ? '#9ca3af' : '#6b7280'}; + `; + + contentDiv.appendChild(titleDiv); + contentDiv.appendChild(descDiv); + + button.appendChild(iconDiv); + button.appendChild(contentDiv); + this.modalBody.appendChild(button); + }); + } + + _handleOptionClick(type) { + console.log('Selected login type:', type); + + // Handle different login types + switch (type) { + case 'extension': + this._handleExtension(); + break; + case 'local': + this._showLocalKeyScreen(); + break; + case 'connect': + this._showConnectScreen(); + break; + case 'readonly': + this._handleReadonly(); + break; + case 'otp': + this._showOtpScreen(); + break; + } + } + + _handleExtension() { + // Detect all available real extensions + const availableExtensions = this._detectAllExtensions(); + + if (availableExtensions.length === 0) { + console.log('Modal: No real extensions found'); + this._showExtensionRequired(); + } else if (availableExtensions.length === 1) { + console.log('Modal detected single extension:', availableExtensions[0].name); + this._tryExtensionLogin(availableExtensions[0].extension); + } else { + console.log('Modal detected multiple extensions:', availableExtensions.map(e => e.name)); + this._showExtensionChoice(availableExtensions); + } + } + + _detectAllExtensions() { + const extensions = []; + + // Check navigator.nostr (NIP-07 standard location) + if (window.navigator?.nostr && typeof window.navigator.nostr.getPublicKey === 'function') { + extensions.push({ + name: 'navigator.nostr', + displayName: 'Standard Extension (navigator.nostr)', + icon: '🌐', + extension: window.navigator.nostr + }); + } + + // Check webln.nostr (Alby WebLN) + if (window.webln?.nostr && typeof window.webln.nostr.getPublicKey === 'function') { + extensions.push({ + name: 'webln.nostr', + displayName: 'Alby WebLN Extension', + icon: '⚡', + extension: window.webln.nostr + }); + } + + // Check alby.nostr (Alby direct) + if (window.alby?.nostr && typeof window.alby.nostr.getPublicKey === 'function') { + extensions.push({ + name: 'alby.nostr', + displayName: 'Alby Extension (Direct)', + icon: '🐝', + extension: window.alby.nostr + }); + } + + // Check nos2x + if (window.nos2x && typeof window.nos2x.getPublicKey === 'function') { + extensions.push({ + name: 'nos2x', + displayName: 'nos2x Extension', + icon: '🔌', + extension: window.nos2x + }); + } + + // Check window.nostr but make sure it's not our library + if (window.nostr && typeof window.nostr.getPublicKey === 'function') { + const isRealExtension = ( + typeof window.nostr._hexToUint8Array !== 'function' && // Our library has this method + window.nostr.constructor.name !== 'Object' // Real extensions usually have proper constructors + ); + + if (isRealExtension) { + // Don't add if we already detected it via another path + const alreadyDetected = extensions.some(ext => ext.extension === window.nostr); + if (!alreadyDetected) { + extensions.push({ + name: 'window.nostr', + displayName: 'Extension (window.nostr)', + icon: '🔑', + extension: window.nostr + }); + } + } + } + + return extensions; + } + + _showExtensionChoice(extensions) { + this.modalBody.innerHTML = ''; + + const title = document.createElement('h3'); + title.textContent = 'Choose Browser Extension'; + title.style.cssText = 'margin: 0 0 16px 0; font-size: 18px; font-weight: 600;'; + + const description = document.createElement('p'); + description.textContent = `Found ${extensions.length} Nostr extensions. Choose which one to use:`; + description.style.cssText = 'margin-bottom: 20px; color: #6b7280; font-size: 14px;'; + + this.modalBody.appendChild(title); + this.modalBody.appendChild(description); + + // Create button for each extension + extensions.forEach((ext, index) => { + const button = document.createElement('button'); + button.onclick = () => this._tryExtensionLogin(ext.extension); + button.style.cssText = ` + display: flex; + align-items: center; + width: 100%; + padding: 16px; + margin-bottom: 12px; + background: ${this.options?.darkMode ? '#374151' : 'white'}; + border: 1px solid ${this.options?.darkMode ? '#4b5563' : '#d1d5db'}; + border-radius: 8px; + cursor: pointer; + transition: all 0.2s; + text-align: left; + `; + + button.onmouseover = () => { + button.style.boxShadow = '0 4px 6px -1px rgba(0, 0, 0, 0.1)'; + button.style.transform = 'translateY(-1px)'; + }; + button.onmouseout = () => { + button.style.boxShadow = 'none'; + button.style.transform = 'none'; + }; + + const iconDiv = document.createElement('div'); + iconDiv.textContent = ext.icon; + iconDiv.style.cssText = ` + font-size: 24px; + margin-right: 16px; + width: 24px; + text-align: center; + `; + + const contentDiv = document.createElement('div'); + contentDiv.style.cssText = 'flex: 1;'; + + const nameDiv = document.createElement('div'); + nameDiv.textContent = ext.displayName; + nameDiv.style.cssText = ` + font-weight: 600; + margin-bottom: 4px; + color: ${this.options?.darkMode ? 'white' : '#1f2937'}; + `; + + const pathDiv = document.createElement('div'); + pathDiv.textContent = ext.name; + pathDiv.style.cssText = ` + font-size: 12px; + color: ${this.options?.darkMode ? '#9ca3af' : '#6b7280'}; + font-family: monospace; + `; + + contentDiv.appendChild(nameDiv); + contentDiv.appendChild(pathDiv); + + button.appendChild(iconDiv); + button.appendChild(contentDiv); + this.modalBody.appendChild(button); + }); + + // Add back button + const backButton = document.createElement('button'); + backButton.textContent = 'Back to Login Options'; + backButton.onclick = () => this._renderLoginOptions(); + backButton.style.cssText = this._getButtonStyle('secondary') + 'margin-top: 20px;'; + + this.modalBody.appendChild(backButton); + } + + async _tryExtensionLogin(extensionObj) { + try { + // Show loading state + this.modalBody.innerHTML = '
🔄 Connecting to extension...
'; + + // Get pubkey from extension + const pubkey = await extensionObj.getPublicKey(); + console.log('Extension provided pubkey:', pubkey); + + // Set extension method with the extension object + this._setAuthMethod('extension', { pubkey, extension: extensionObj }); + + } catch (error) { + console.error('Extension login failed:', error); + this._showError(`Extension login failed: ${error.message}`); + } + } + + _showLocalKeyScreen() { + this.modalBody.innerHTML = ''; + + const title = document.createElement('h3'); + title.textContent = 'Local Key'; + title.style.cssText = 'margin: 0 0 20px 0; font-size: 18px; font-weight: 600;'; + + const createButton = document.createElement('button'); + createButton.textContent = 'Create New Key'; + createButton.onclick = () => this._createLocalKey(); + createButton.style.cssText = this._getButtonStyle(); + + const importButton = document.createElement('button'); + importButton.textContent = 'Import Existing Key'; + importButton.onclick = () => this._showImportKeyForm(); + importButton.style.cssText = this._getButtonStyle() + 'margin-top: 12px;'; + + const backButton = document.createElement('button'); + backButton.textContent = 'Back'; + backButton.onclick = () => this._renderLoginOptions(); + backButton.style.cssText = ` + display: block; + margin-top: 20px; + padding: 12px; + background: #6b7280; + color: white; + border: none; + border-radius: 6px; + cursor: pointer; + `; + + this.modalBody.appendChild(title); + this.modalBody.appendChild(createButton); + this.modalBody.appendChild(importButton); + this.modalBody.appendChild(backButton); + } + + _createLocalKey() { + try { + const sk = window.NostrTools.generateSecretKey(); + const pk = window.NostrTools.getPublicKey(sk); + const nsec = window.NostrTools.nip19.nsecEncode(sk); + const npub = window.NostrTools.nip19.npubEncode(pk); + + this._showKeyDisplay(pk, nsec, 'created'); + } catch (error) { + this._showError('Failed to create key: ' + error.message); + } + } + + _showImportKeyForm() { + this.modalBody.innerHTML = ''; + + const title = document.createElement('h3'); + title.textContent = 'Import Local Key'; + title.style.cssText = 'margin: 0 0 16px 0; font-size: 18px; font-weight: 600;'; + + const description = document.createElement('p'); + description.textContent = 'Enter your secret key in either nsec or hex format:'; + description.style.cssText = 'margin-bottom: 12px; color: #6b7280; font-size: 14px;'; + + const textarea = document.createElement('textarea'); + textarea.placeholder = 'Enter your secret key:\n• nsec1... (bech32 format)\n• 64-character hex string'; + textarea.style.cssText = ` + width: 100%; + height: 100px; + padding: 12px; + border: 1px solid #d1d5db; + border-radius: 6px; + margin-bottom: 12px; + resize: none; + font-family: monospace; + font-size: 14px; + box-sizing: border-box; + `; + + // Add real-time format detection + const formatHint = document.createElement('div'); + formatHint.style.cssText = 'margin-bottom: 16px; font-size: 12px; color: #6b7280; min-height: 16px;'; + + textarea.oninput = () => { + const value = textarea.value.trim(); + if (!value) { + formatHint.textContent = ''; + return; + } + + const format = this._detectKeyFormat(value); + if (format === 'nsec') { + formatHint.textContent = '✅ Valid nsec format detected'; + formatHint.style.color = '#059669'; + } else if (format === 'hex') { + formatHint.textContent = '✅ Valid hex format detected'; + formatHint.style.color = '#059669'; + } else { + formatHint.textContent = '❌ Invalid key format - must be nsec1... or 64-character hex'; + formatHint.style.color = '#dc2626'; + } + }; + + const importButton = document.createElement('button'); + importButton.textContent = 'Import Key'; + importButton.onclick = () => this._importLocalKey(textarea.value); + importButton.style.cssText = this._getButtonStyle(); + + const backButton = document.createElement('button'); + backButton.textContent = 'Back'; + backButton.onclick = () => this._showLocalKeyScreen(); + backButton.style.cssText = this._getButtonStyle('secondary') + 'margin-top: 12px;'; + + this.modalBody.appendChild(title); + this.modalBody.appendChild(description); + this.modalBody.appendChild(textarea); + this.modalBody.appendChild(formatHint); + this.modalBody.appendChild(importButton); + this.modalBody.appendChild(backButton); + } + + _detectKeyFormat(keyValue) { + const trimmed = keyValue.trim(); + + // Check for nsec format + if (trimmed.startsWith('nsec1') && trimmed.length === 63) { + try { + window.NostrTools.nip19.decode(trimmed); + return 'nsec'; + } catch { + return 'invalid'; + } + } + + // Check for hex format (64 characters, valid hex) + if (trimmed.length === 64 && /^[a-fA-F0-9]{64}$/.test(trimmed)) { + return 'hex'; + } + + return 'invalid'; + } + + _importLocalKey(keyValue) { + try { + const trimmed = keyValue.trim(); + if (!trimmed) { + throw new Error('Please enter a secret key'); + } + + const format = this._detectKeyFormat(trimmed); + let sk; + + if (format === 'nsec') { + // Decode nsec format - this returns Uint8Array + const decoded = window.NostrTools.nip19.decode(trimmed); + if (decoded.type !== 'nsec') { + throw new Error('Invalid nsec format'); + } + sk = decoded.data; // This is already Uint8Array + } else if (format === 'hex') { + // Convert hex string to Uint8Array + sk = this._hexToUint8Array(trimmed); + // Test that it's a valid secret key by trying to get public key + window.NostrTools.getPublicKey(sk); + } else { + throw new Error('Invalid key format. Please enter either nsec1... or 64-character hex string'); + } + + // Generate public key and encoded formats + const pk = window.NostrTools.getPublicKey(sk); + const nsec = window.NostrTools.nip19.nsecEncode(sk); + const npub = window.NostrTools.nip19.npubEncode(pk); + + this._showKeyDisplay(pk, nsec, 'imported'); + } catch (error) { + this._showError('Invalid key: ' + error.message); + } + } + + _hexToUint8Array(hex) { + // Convert hex string to Uint8Array + if (hex.length % 2 !== 0) { + throw new Error('Invalid hex string length'); + } + const bytes = new Uint8Array(hex.length / 2); + for (let i = 0; i < bytes.length; i++) { + bytes[i] = parseInt(hex.substr(i * 2, 2), 16); + } + return bytes; + } + + _showKeyDisplay(pubkey, nsec, action) { + this.modalBody.innerHTML = ''; + + const title = document.createElement('h3'); + title.textContent = `Key ${action} successfully!`; + title.style.cssText = 'margin: 0 0 16px 0; font-size: 18px; font-weight: 600; color: #059669;'; + + const warningDiv = document.createElement('div'); + warningDiv.textContent = '⚠️ Save your secret key securely!'; + warningDiv.style.cssText = 'background: #fef3c7; color: #92400e; padding: 12px; border-radius: 6px; margin-bottom: 16px; font-size: 14px;'; + + const nsecDiv = document.createElement('div'); + nsecDiv.innerHTML = `Your Secret Key:
${nsec}`; + nsecDiv.style.cssText = 'margin-bottom: 16px; font-size: 14px;'; + + const npubDiv = document.createElement('div'); + npubDiv.innerHTML = `Your Public Key:
${window.NostrTools.nip19.npubEncode(pubkey)}`; + npubDiv.style.cssText = 'margin-bottom: 16px; font-size: 14px;'; + + const continueButton = document.createElement('button'); + continueButton.textContent = 'Continue'; + continueButton.onclick = () => this._setAuthMethod('local', { secret: nsec, pubkey }); + continueButton.style.cssText = this._getButtonStyle(); + + this.modalBody.appendChild(title); + this.modalBody.appendChild(warningDiv); + this.modalBody.appendChild(nsecDiv); + this.modalBody.appendChild(npubDiv); + this.modalBody.appendChild(continueButton); + } + + _setAuthMethod(method, options = {}) { + // Emit auth method selection + const event = new CustomEvent('nlMethodSelected', { + detail: { method, ...options } + }); + window.dispatchEvent(event); + + this.close(); + } + + _showError(message) { + this.modalBody.innerHTML = ''; + + const errorDiv = document.createElement('div'); + errorDiv.style.cssText = 'background: #fee2e2; color: #dc2626; padding: 16px; border-radius: 6px; margin-bottom: 16px;'; + errorDiv.innerHTML = `Error: ${message}`; + + const backButton = document.createElement('button'); + backButton.textContent = 'Back'; + backButton.onclick = () => this._renderLoginOptions(); + backButton.style.cssText = this._getButtonStyle('secondary'); + + this.modalBody.appendChild(errorDiv); + this.modalBody.appendChild(backButton); + } + + _showExtensionRequired() { + this.modalBody.innerHTML = ''; + + const title = document.createElement('h3'); + title.textContent = 'Browser Extension Required'; + title.style.cssText = 'margin: 0 0 16px 0; font-size: 18px; font-weight: 600;'; + + const message = document.createElement('p'); + message.textContent = 'Please install a Nostr browser extension like Alby or getflattr and refresh the page.'; + message.style.cssText = 'margin-bottom: 20px; color: #6b7280;'; + + const backButton = document.createElement('button'); + backButton.textContent = 'Back'; + backButton.onclick = () => this._renderLoginOptions(); + backButton.style.cssText = this._getButtonStyle('secondary'); + + this.modalBody.appendChild(title); + this.modalBody.appendChild(message); + this.modalBody.appendChild(backButton); + } + + _showConnectScreen() { + this.modalBody.innerHTML = ''; + + const title = document.createElement('h3'); + title.textContent = 'Connect to NIP-46 Remote Signer'; + title.style.cssText = 'margin: 0 0 16px 0; font-size: 18px; font-weight: 600;'; + + const description = document.createElement('p'); + description.textContent = 'Connect to a remote signer (bunker) server to use its keys for signing.'; + description.style.cssText = 'margin-bottom: 20px; color: #6b7280; font-size: 14px;'; + + const formGroup = document.createElement('div'); + formGroup.style.cssText = 'margin-bottom: 20px;'; + + const label = document.createElement('label'); + label.textContent = 'Bunker Public Key:'; + label.style.cssText = 'display: block; margin-bottom: 8px; font-weight: 500;'; + + const pubkeyInput = document.createElement('input'); + pubkeyInput.type = 'text'; + pubkeyInput.placeholder = 'bunker://pubkey?relay=..., bunker:hex, hex, or npub...'; + pubkeyInput.style.cssText = ` + width: 100%; + padding: 12px; + border: 1px solid #d1d5db; + border-radius: 6px; + margin-bottom: 12px; + font-family: monospace; + box-sizing: border-box; + `; + + const urlLabel = document.createElement('label'); + urlLabel.textContent = 'Remote URL (optional):'; + urlLabel.style.cssText = 'display: block; margin-bottom: 8px; font-weight: 500;'; + + const urlInput = document.createElement('input'); + urlInput.type = 'url'; + urlInput.placeholder = 'ws://localhost:8080 (default)'; + urlInput.style.cssText = ` + width: 100%; + padding: 12px; + border: 1px solid #d1d5db; + border-radius: 6px; + margin-bottom: 16px; + box-sizing: border-box; + `; + + // Pre-fill with our bunker config if available + if (window.NIP46_BUNKER_CONFIG) { + pubkeyInput.value = window.NIP46_BUNKER_CONFIG.remoteSigner.pubkey; + urlInput.value = window.NIP46_BUNKER_CONFIG.remoteSigner.url; + } + + const connectButton = document.createElement('button'); + connectButton.textContent = 'Connect to Bunker'; + connectButton.onclick = () => this._handleNip46Connect(pubkeyInput.value, urlInput.value); + connectButton.style.cssText = this._getButtonStyle(); + + const backButton = document.createElement('button'); + backButton.textContent = 'Back'; + backButton.onclick = () => this._renderLoginOptions(); + backButton.style.cssText = this._getButtonStyle('secondary') + 'margin-top: 12px;'; + + formGroup.appendChild(label); + formGroup.appendChild(pubkeyInput); + formGroup.appendChild(urlLabel); + formGroup.appendChild(urlInput); + + this.modalBody.appendChild(title); + this.modalBody.appendChild(description); + this.modalBody.appendChild(formGroup); + this.modalBody.appendChild(connectButton); + this.modalBody.appendChild(backButton); + } + + _handleNip46Connect(bunkerPubkey, bunkerUrl) { + if (!bunkerPubkey || !bunkerPubkey.length) { + this._showError('Bunker pubkey is required'); + return; + } + + this._showNip46Connecting(bunkerPubkey, bunkerUrl); + this._performNip46Connect(bunkerPubkey, bunkerUrl); + } + + _showNip46Connecting(bunkerPubkey, bunkerUrl) { + this.modalBody.innerHTML = ''; + + const title = document.createElement('h3'); + title.textContent = 'Connecting to Remote Signer...'; + title.style.cssText = 'margin: 0 0 16px 0; font-size: 18px; font-weight: 600; color: #059669;'; + + const description = document.createElement('p'); + description.textContent = 'Establishing secure connection to your remote signer.'; + description.style.cssText = 'margin-bottom: 20px; color: #6b7280;'; + + // Normalize bunker pubkey for display (= show original format if bunker: prefix) + const displayPubkey = bunkerPubkey.startsWith('bunker:') || bunkerPubkey.startsWith('npub') || bunkerPubkey.length === 64 ? bunkerPubkey : bunkerPubkey; + + const bunkerInfo = document.createElement('div'); + bunkerInfo.style.cssText = 'background: #f1f5f9; padding: 12px; border-radius: 6px; margin-bottom: 20px; font-size: 14px;'; + bunkerInfo.innerHTML = ` + Connecting to bunker:
+ Pubkey: ${displayPubkey}
+ Relay: ${bunkerUrl || 'ws://localhost:8080'}
+ If this relay is offline, the bunker server may be unavailable. + `; + + const connectingDiv = document.createElement('div'); + connectingDiv.style.cssText = 'text-align: center; color: #6b7280;'; + connectingDiv.innerHTML = ` +
+
Please wait while we establish the connection...
+
This may take a few seconds
+ `; + + this.modalBody.appendChild(title); + this.modalBody.appendChild(description); + this.modalBody.appendChild(bunkerInfo); + this.modalBody.appendChild(connectingDiv); + } + + async _performNip46Connect(bunkerPubkey, bunkerUrl) { + try { + console.log('Starting NIP-46 connection to bunker:', bunkerPubkey, bunkerUrl); + + // Check if nostr-tools NIP-46 is available + if (!window.NostrTools?.nip46) { + throw new Error('nostr-tools NIP-46 module not available'); + } + + // Use nostr-tools to parse bunker input - this handles all formats correctly + console.log('Parsing bunker input with nostr-tools...'); + const bunkerPointer = await window.NostrTools.nip46.parseBunkerInput(bunkerPubkey); + + if (!bunkerPointer) { + throw new Error('Unable to parse bunker connection string or resolve NIP-05 identifier'); + } + + console.log('Parsed bunker pointer:', bunkerPointer); + + // Create local client keypair for this session + const localSecretKey = window.NostrTools.generateSecretKey(); + console.log('Generated local client keypair for NIP-46 session'); + + // Use nostr-tools BunkerSigner.fromBunker() for bunker:// connections + console.log('Creating nip46 BunkerSigner...'); + const signer = window.NostrTools.nip46.BunkerSigner.fromBunker(localSecretKey, bunkerPointer, { + onauth: (url) => { + console.log('Received auth URL from bunker:', url); + // Open auth URL in popup or redirect + window.open(url, '_blank', 'width=600,height=800'); + } + }); + + console.log('NIP-46 BunkerSigner created successfully'); + + // Attempt initial ping to verify connection + console.log('Testing bunker connection with ping...'); + await signer.ping(); + console.log('NIP-46 ping successful - bunker is reachable'); + + // Try to connect (this may trigger auth flow) + console.log('Attempting NIP-46 connect...'); + await signer.connect(); + console.log('NIP-46 connect successful'); + + // Get the user's public key from the bunker + console.log('Getting public key from bunker...'); + const userPubkey = await signer.getPublicKey(); + console.log('NIP-46 user public key:', userPubkey); + + // Store the NIP-46 authentication info + const nip46Info = { + pubkey: userPubkey, + signer: { + method: 'nip46', + remotePubkey: bunkerPointer.pubkey, + bunkerSigner: signer, + secret: bunkerPointer.secret, + relays: bunkerPointer.relays + } + }; + + console.log('NOSTR_LOGIN_LITE NIP-46 connection established successfully!'); + + // Set as current auth method + this._setAuthMethod('nip46', nip46Info); + return; + + } catch (error) { + console.error('NIP-46 connection failed:', error); + this._showNip46Error(error.message); + } + } + + _showNip46Error(message) { + this.modalBody.innerHTML = ''; + + const title = document.createElement('h3'); + title.textContent = 'Connection Failed'; + title.style.cssText = 'margin: 0 0 16px 0; font-size: 18px; font-weight: 600; color: #dc2626;'; + + const errorMsg = document.createElement('p'); + errorMsg.textContent = `Unable to connect to remote signer: ${message}`; + errorMsg.style.cssText = 'margin-bottom: 20px; color: #6b7280;'; + + const retryButton = document.createElement('button'); + retryButton.textContent = 'Try Again'; + retryButton.onclick = () => this._showConnectScreen(); + retryButton.style.cssText = this._getButtonStyle(); + + const backButton = document.createElement('button'); + backButton.textContent = 'Back to Options'; + backButton.onclick = () => this._renderLoginOptions(); + backButton.style.cssText = this._getButtonStyle('secondary') + 'margin-top: 12px;'; + + this.modalBody.appendChild(title); + this.modalBody.appendChild(errorMsg); + this.modalBody.appendChild(retryButton); + this.modalBody.appendChild(backButton); + } + + _handleReadonly() { + // Set read-only mode + this._setAuthMethod('readonly'); + } + + _showOtpScreen() { + // Placeholder for OTP functionality + this._showError('OTP/DM not yet implemented - coming soon!'); + } + + _getButtonStyle(type = 'primary') { + const baseStyle = ` + display: block; + width: 100%; + padding: 12px; + border: none; + border-radius: 8px; + font-size: 16px; + font-weight: 500; + cursor: pointer; + transition: all 0.2s; + `; + + if (type === 'primary') { + return baseStyle + ` + background: #3b82f6; + color: white; + `; + } else { + return baseStyle + ` + background: #6b7280; + color: white; + `; + } + } + + // Public API + static init(options) { + if (Modal.instance) return Modal.instance; + Modal.instance = new Modal(options); + return Modal.instance; + } + + static getInstance() { + return Modal.instance; + } +} + +// Initialize global instance +let modalInstance = null; + +window.addEventListener('load', () => { + modalInstance = new Modal(); +}); + + +// ====================================== +// nip46-client.js +// ====================================== + + +class NIP46Client { + constructor() { + this.pool = null; + this.localSk = null; + this.localPk = null; + this.remotePk = null; + this.relays = []; + this.sub = null; + this.pendingRequests = {}; + this.useNip44 = false; + this.iframeOrigin = null; + this.iframePort = null; + } + + init(localSk, remotePk, relays, iframeOrigin) { + // Create SimplePool + this.pool = new window.NostrTools.SimplePool(); + + // Setup keys + this.localSk = localSk; + if (this.localSk) { + this.localPk = window.NostrTools.getPublicKey(this.localSk); + } + + this.remotePk = remotePk; + this.relays = [...relays]; + + // Store iframe origin for future use + this.iframeOrigin = iframeOrigin; + + console.log('NIP46Client initialized for', this.remotePk ? 'remote signer' : 'listening mode'); + } + + setUseNip44(use) { + this.useNip44 = use; + } + + subscribeReplies() { + if (!this.pool || !this.localPk) return; + + // Subscribe to replies to our pubkey on kind 24133 (NIP-46 methods) + this.sub = this.pool.sub(this.relays, [{ + kinds: [24133], + '#p': [this.localPk] + }]); + + this.sub.on('event', (event) => this.onEvent(event)); + this.sub.on('eose', () => { + console.log('NIP-46 subscription caught up'); + }); + + console.log('Subscribed to NIP-46 replies on relays:', this.relays); + } + + unsubscribe() { + if (this.sub) { + this.sub.unsub(); + this.sub = null; + } + } + + async onEvent(event) { + console.log('NIP-46 event received:', event); + + try { + const parsed = await this.parseEvent(event); + if (parsed) { + if (parsed.id && this.pendingRequests[parsed.id]) { + // Handle response + const handler = this.pendingRequests[parsed.id]; + delete this.pendingRequests[parsed.id]; + + if (parsed.result !== undefined) { + handler.resolve(parsed.result); + } else if (parsed.error) { + handler.reject(new Error(parsed.error)); + } else { + handler.reject(new Error('Invalid response format')); + } + } else if (parsed.method === 'auth_url') { + // Handle auth_url emissions (deduplication required) + this.emitAuthUrlIfNeeded(parsed.params[0]); + } + } + } catch (error) { + console.error('Error processing NIP-46 event:', error); + } + } + + emitAuthUrlIfNeeded(url) { + // Deduplicate auth_url emissions - only emit if not recently shown + const lastUrl = sessionStorage.getItem('nl-last-auth-url'); + if (lastUrl === url) { + console.log('Auth URL already shown, skipping duplicate:', url); + return; + } + + sessionStorage.setItem('nl-last-auth-url', url); + console.log('New auth URL:', url); + + // Emit event for UI + window.dispatchEvent(new CustomEvent('nlAuthUrl', { detail: { url } })); + } + + async parseEvent(event) { + try { + let content = event.content; + + // Determine encryption method based on content structure + if (content.length > 44) { + // Likely NIP-44 (encrypted) + if (this.localSk && event.pubkey) { + try { + content = window.NostrTools.nip44?.decrypt(this.localSk, event.pubkey, content); + } catch (e) { + console.warn('NIP-44 decryption failed, trying NIP-04...'); + content = await window.NostrTools.nip04.decrypt(this.localSk, event.pubkey, content); + } + } + } else { + // Likely NIP-04 + if (this.localSk && event.pubkey) { + content = await window.NostrTools.nip04.decrypt(this.localSk, event.pubkey, content); + } + } + + const payload = JSON.parse(content); + console.log('Decrypted NIP-46 payload:', payload); + + return { + id: payload.id, + method: payload.method, + params: payload.params, + result: payload.result, + error: payload.error, + event: event + }; + + } catch (e) { + console.error('Failed to parse event:', e); + return null; + } + } + + async listen(nostrConnectSecret) { + return new Promise((resolve, reject) => { + if (!this.localPk) { + reject(new Error('No local pubkey available for listening')); + return; + } + + // Subscribe to unsolicited events to our pubkey + let foundSecretOrAck = false; + + const listenSub = this.pool.sub(this.relays, [{ + kinds: [24133], + '#p': [this.localPk] + }]); + + listenSub.on('event', async (event) => { + try { + const parsed = await this.parseEvent(event); + if (parsed && parsed.method === 'connect') { + // Accept if it's an ack or matches our secret + const [userPubkey, token] = parsed.params || []; + + if (token === '' && parsed.result === 'ack') { + // Ack received + foundSecretOrAck = true; + listenSub.unsub(); + resolve(event.pubkey); + } else if (token === nostrConnectSecret) { + // Secret match + foundSecretOrAck = true; + listenSub.unsub(); + resolve(event.pubkey); + } + } + } catch (error) { + console.error('Error in listen mode:', error); + } + }); + + // Timeout after 5 minutes + setTimeout(() => { + if (!foundSecretOrAck) { + listenSub.unsub(); + reject(new Error('Listen timeout - no signer connected')); + } + }, 300000); + }); + } + + async connect(token, perms) { + return new Promise(async (resolve, reject) => { + try { + const result = await this.sendRequest( + this.remotePk, + 'connect', + [this.localPk, token || '', perms || ''], + 24133, + (response) => { + if (response === 'ack') { + resolve(true); + } else { + reject(new Error('Connection not acknowledged')); + } + } + ); + + // Set 30 second timeout + setTimeout(() => reject(new Error('Connection timeout')), 30000); + + } catch (error) { + reject(error); + } + }); + } + + async initUserPubkey(hint) { + if (hint) { + this.remotePk = hint; + return hint; + } + + if (!this.remotePk) { + // Request get_public_key + return new Promise(async (resolve, reject) => { + try { + const pubkey = await this.sendRequest( + this.remotePk, + 'get_public_key', + [], + 24133 + ); + this.remotePk = pubkey; + resolve(pubkey); + } catch (error) { + reject(error); + } + }); + } + + return this.remotePk; + } + + async sendRequest(remotePubkey, method, params, kind = 24133, cb) { + if (!this.pool || !this.localSk || !this.localPk) { + throw new Error('NIP46Client not properly initialized'); + } + + if (!remotePubkey) { + throw new Error('No remote pubkey specified'); + } + + const id = this._generateId(); + + // Create request event + const event = await this.createRequestEvent(id, remotePubkey, method, params, kind); + + console.log('Sending NIP-46 request:', { id, method, params }); + + // Publish to relays + const pubs = await this.pool.publish(this.relays, event); + console.log('Published to relays, waiting for response...'); + + return new Promise((resolve, reject) => { + // Set timeout + const timeout = setTimeout(() => { + console.error('NIP-46 request timeout for id:', id); + delete this.pendingRequests[id]; + reject(new Error(`Request timeout for ${method}`)); + }, 60000); // 1 minute timeout + + // Store handler + this.pendingRequests[id] = { + resolve: (result) => { + clearTimeout(timeout); + resolve(result); + }, + reject: (error) => { + clearTimeout(timeout); + reject(error); + }, + timestamp: Date.now() + }; + + // If callback provided, override resolve + if (cb) { + const originalResolve = this.pendingRequests[id].resolve; + this.pendingRequests[id].resolve = (result) => { + cb(result); + originalResolve(result); + }; + } + }); + } + + async createRequestEvent(id, remotePubkey, method, params, kind = 24133) { + let content = JSON.stringify({ + id, + method, + params + }); + + // Choose encryption method + let encrypted = content; + if (method !== 'create_account') { + // Use NIP-44 for non-account creation methods if available + if (this.useNip44 && window.NostrTools.nip44) { + encrypted = window.NostrTools.nip44.encrypt(this.localSk, remotePubkey, content); + } else { + // Fallback to NIP-04 + encrypted = await window.NostrTools.nip04.encrypt(this.localSk, remotePubkey, content); + } + } + + // Create event structure + const event = { + kind: kind, + content: encrypted, + tags: [ + ['p', remotePubkey] + ], + created_at: Math.floor(Date.now() / 1000), + pubkey: this.localPk, + id: '', // Will be set by finalizeEvent + sig: '' // Will be set by finalizeEvent + }; + + // Sign the event + const signedEvent = window.NostrTools.finalizeEvent(event, this.localSk); + + return signedEvent; + } + + _generateId() { + return 'nl-' + Date.now() + '-' + Math.random().toString(36).substring(2, 15); + } + + setWorkerIframePort(port) { + this.iframePort = port; + + // Set up postMessage routing if needed + if (this.iframePort && this.iframeOrigin) { + this.iframePort.onmessage = (event) => { + if (event.origin !== this.iframeOrigin) { + console.warn('Ignoring message from unknown origin:', event.origin); + return; + } + + console.log('Received iframe message:', event.data); + // Handle iframe messages + }; + + // Send keepalive + setInterval(() => { + if (this.iframePort) { + try { + this.iframePort.postMessage({ type: 'ping' }); + } catch (e) { + console.warn('Iframe port closed'); + this.iframePort = null; + } + } + }, 30000); // 30 seconds + } + } + + teardown() { + this.unsubscribe(); + + if (this.iframePort) { + try { + this.iframePort.close(); + } catch (e) { + console.warn('Error closing iframe port:', e); + } + this.iframePort = null; + } + + if (this.pool) { + this.pool.close(this.relays); + this.pool = null; + } + + // Clear all pending requests + for (const id in this.pendingRequests) { + this.pendingRequests[id].reject(new Error('Client teardown')); + } + this.pendingRequests = {}; + } +} + + +// ====================================== +// nostr-login-lite.js +// ====================================== + + +// Import NIP-46 client +if (typeof NIP46Client === 'undefined') { + // Load NIP46Client if not already available (for non-bundled version) + const script = document.createElement('script'); + script.src = './core/nip46-client.js'; + document.head.appendChild(script); +} + +// Global state +const LiteState = { + initialized: false, + windowNostr: null, + options: null, + auth: null, + modal: null, + bus: null, + pool: null, + nip44Codec: null, + extensionBridge: null, + nip46Client: null +}; + +// Dependencies verification +class Deps { + static ensureNostrToolsLoaded() { + if (typeof window === 'undefined') { + throw new Error('NOSTR_LOGIN_LITE must run in browser environment'); + } + + if (!window.NostrTools) { + throw new Error( + 'window.NostrTools is required but not loaded. ' + + 'Please include: ' + ); + } + + // Verify required APIs + const required = ['SimplePool', 'getPublicKey', 'finalizeEvent', 'nip04']; + for (const api of required) { + if (!window.NostrTools[api]) { + throw new Error(`window.NostrTools.${api} is required but missing`); + } + } + + // Check for key generation function (might be generateSecretKey or generatePrivateKey) + if (!window.NostrTools.generateSecretKey && !window.NostrTools.generatePrivateKey) { + throw new Error('window.NostrTools must have either generateSecretKey or generatePrivateKey'); + } + + return true; + } +} + +// Event Bus for internal communication +class Bus { + constructor() { + this.handlers = {}; + } + + on(event, handler) { + if (!this.handlers[event]) { + this.handlers[event] = []; + } + this.handlers[event].push(handler); + } + + off(event, handler) { + if (!this.handlers[event]) return; + this.handlers[event] = this.handlers[event].filter(h => h !== handler); + } + + emit(event, payload) { + if (!this.handlers[event]) return; + this.handlers[event].forEach(handler => { + try { + handler(payload); + } catch (e) { + console.error(`Error in event handler for ${event}:`, e); + } + }); + } +} + +// Storage helpers +class Store { + static addAccount(info) { + const accounts = this.getAccounts(); + // Remove existing account with same pubkey if present + const filtered = accounts.filter(acc => acc.pubkey !== info.pubkey); + filtered.push(info); + localStorage.setItem('nl_accounts', JSON.stringify(filtered)); + } + + static removeCurrentAccount() { + const current = this.getCurrent(); + if (current && current.pubkey) { + const accounts = this.getAccounts(); + const filtered = accounts.filter(acc => acc.pubkey !== current.pubkey); + localStorage.setItem('nl_accounts', JSON.stringify(filtered)); + localStorage.removeItem('nl_current'); + } + } + + static getCurrent() { + try { + const stored = localStorage.getItem('nl_current'); + return stored ? JSON.parse(stored) : null; + } catch (e) { + console.error('Error parsing current account:', e); + return null; + } + } + + static setCurrent(info) { + localStorage.setItem('nl_current', JSON.stringify(info)); + } + + static getAccounts() { + try { + const stored = localStorage.getItem('nl_accounts'); + return stored ? JSON.parse(stored) : []; + } catch (e) { + console.error('Error parsing accounts:', e); + return []; + } + } + + static getRecents() { + // Return last 5 used accounts in reverse chronological order + const accounts = this.getAccounts().slice(-5).reverse(); + return accounts; + } + + static setItem(key, value) { + localStorage.setItem(`nl-${key}`, value); + } + + static getItem(key) { + return localStorage.getItem(`nl-${key}`); + } + + static async getIcon() { + // Simple default icon - could be extended to fetch from profile + return '🔑'; + } +} + +// Relay configuration helpers +class Relays { + static getDefaultRelays(options) { + if (options?.relays) { + return this.normalize(options.relays); + } + + // Default relays for fallbacks + return [ + 'wss://relay.damus.io', + 'wss://relay.snort.social', + 'wss://nos.lol' + ]; + } + + static normalize(relays) { + return relays.map(relay => { + // Ensure wss:// prefix + if (relay.startsWith('ws://')) { + return relay.replace('ws://', 'wss://'); + } else if (!relay.startsWith('wss://')) { + return `wss://${relay}`; + } + return relay; + }).filter(relay => { + // Remove duplicates and validate URLs + try { + new URL(relay); + return true; + } catch { + return false; + } + }).filter((relay, index, self) => self.indexOf(relay) === index); // dedupe + } +} + +// Minimal NIP-44 codec fallback +class Nip44 { + constructor() { + this.Nip44 = null; + // Initialize with existing codec if available + this.nip44Available = window.NostrTools?.nip44; + } + + static encrypt(ourSk, theirPk, plaintext) { + if (window.NostrTools?.nip44?.encrypt) { + return window.NostrTools.nip44.encrypt(ourSk, theirPk, plaintext); + } + + throw new Error('NIP-44 encryption not available. Please use nostr-tools@>=2.x or provide codec implementation.'); + } + + static decrypt(ourSk, theirPk, ciphertext) { + if (window.NostrTools?.nip44?.decrypt) { + return window.NostrTools.nip44.decrypt(ourSk, theirPk, ciphertext); + } + + throw new Error('NIP-44 decryption not available. Please use nostr-tools@>=2.x or provide codec implementation.'); + } +} + +// LocalSigner wrapping window.NostrTools +class LocalSigner { + constructor(sk) { + this.sk = sk; + // Generate pubkey from secret key + this.pk = this._getPubKey(); + } + + _getPubKey() { + const seckey = this.sk.startsWith('nsec') ? + window.NostrTools.nip19.decode(this.sk).data : + this.sk; + return window.NostrTools.getPublicKey(seckey); + } + + pubkey() { + return this.pk; + } + + async sign(event) { + // Prepare event for signing + const ev = { ...event }; + ev.pubkey = this.pk; + + // Generate event ID and sign + const signedEvent = await window.NostrTools.finalizeEvent(ev, this.sk); + return signedEvent; + } + + async encrypt04(pubkey, plaintext) { + return await window.NostrTools.nip04.encrypt(this.sk, pubkey, plaintext); + } + + async decrypt04(pubkey, ciphertext) { + return await window.NostrTools.nip04.decrypt(this.sk, pubkey, ciphertext); + } + + async encrypt44(pubkey, plaintext) { + return Nip44.encrypt(this.sk, pubkey, plaintext); + } + + async decrypt44(pubkey, ciphertext) { + return Nip44.decrypt(this.sk, pubkey, ciphertext); + } +} + +// ExtensionBridge for detecting and managing browser extensions +class ExtensionBridge { + constructor() { + this.checking = false; + this.checkInterval = null; + this.originalNostr = null; + this.foundExtension = null; + } + + startChecking(nostrLite) { + if (this.checking) return; + this.checking = true; + + const check = () => { + this.initExtension(nostrLite); + }; + + // Check immediately + check(); + + // Then check every 200ms for 30 seconds + this.checkInterval = setInterval(check, 200); + + // Stop checking after 30 seconds + setTimeout(() => { + clearInterval(this.checkInterval); + this.checkInterval = null; + }, 30000); + } + + initExtension(nostrLite, lastTry = false) { + const extension = window.nostr; + + if (extension && !this.foundExtension) { + // Check if this is actually a real extension, not our own library + const isRealExtension = ( + extension !== nostrLite && // Not the same object we're about to assign + extension !== windowNostr && // Not our windowNostr object + typeof extension._hexToUint8Array !== 'function' && // Our library has this internal method + extension.constructor.name !== 'Object' // Real extensions usually have proper constructors + ); + + if (isRealExtension) { + this.foundExtension = extension; + + // Cache the extension and reassign window.nostr to our lite version + this.originalNostr = window.nostr; + window.nostr = nostrLite; + + console.log('Real Nostr extension detected and bridged:', extension.constructor.name); + + // If currently authenticated, reconcile state + if (LiteState.auth?.signer?.method === 'extension') { + this.reconcileExtension(); + } + } else { + console.log('Skipping non-extension object on window.nostr:', extension.constructor.name); + } + } + } + + hasExtension() { + return !!this.foundExtension; + } + + async setExtensionReadPubkey(expectedPubkey = null) { + if (!this.foundExtension) return false; + + try { + // Temporarily set window.nostr to extension + const temp = window.nostr; + window.nostr = this.foundExtension; + + const pubkey = await this.foundExtension.getPublicKey(); + + // Restore our lite implementation + window.nostr = temp; + + if (expectedPubkey && pubkey !== expectedPubkey) { + console.warn(`Extension pubkey ${pubkey} does not match expected ${expectedPubkey}`); + } + + return pubkey; + } catch (e) { + console.error('Error reading extension pubkey:', e); + return null; + } + } + + trySetForPubkey(expectedPubkey) { + if (!this.hasExtension()) return false; + + this.setExtensionReadPubkey(expectedPubkey).then(pubkey => { + if (pubkey) { + LiteState.bus?.emit('extensionLogin', { pubkey }); + } + }); + + return true; + } + + setExtension() { + if (!this.foundExtension) return; + window.nostr = this.foundExtension; + this.setExtensionReadPubkey().then(pubkey => { + if (pubkey) { + LiteState.bus?.emit('extensionSet', { pubkey }); + } + }); + } + + unset(nostrLite) { + window.nostr = nostrLite; + } + + reconcileExtension() { + // Handle extension state changes + this.setExtensionReadPubkey().then(pubkey => { + if (pubkey) { + // Update current account if extension is the signer + const current = Store.getCurrent(); + if (current && current.signer?.method === 'extension') { + const info = { + ...current, + pubkey, + signer: { method: 'extension' } + }; + Store.setCurrent(info); + LiteState.bus?.emit('authStateUpdate', info); + } + } + }); + } +} + +// Main API surface +class NostrLite { + static async init(options = {}) { + // Ensure dependencies are loaded + Deps.ensureNostrToolsLoaded(); + + // Prevent double initialization + if (LiteState.initialized) { + console.warn('NOSTR_LOGIN_LITE already initialized'); + return; + } + + // Initialize components + LiteState.bus = new Bus(); + LiteState.extensionBridge = new ExtensionBridge(); + + // Initialize NIP-46 client + LiteState.nip46Client = new NIP46Client(); + + // Store options + LiteState.options = { + theme: 'light', + darkMode: false, + relays: Relays.getDefaultRelays(options), + methods: { + connect: true, + extension: true, + local: true, + readonly: true, + otp: true + }, + otp: {}, + ...options + }; + + // Start extension detection + LiteState.extensionBridge.startChecking(windowNostr); + + // Setup auth methods + this._setupAuth(); + + // Initialize modal UI + // this._initModal(); + + console.log('NOSTR_LOGIN_LITE initialized with options:', LiteState.options); + LiteState.initialized = true; + } + + static _setupAuth() { + // Set up event listeners for modal interactions + window.addEventListener('nlMethodSelected', (event) => { + this._handleMethodSelected(event.detail); + }); + + // Set up other auth-related event listeners + this._setupAuthEventListeners(); + + console.log('Auth system setup loaded'); + } + + static _setupAuthEventListeners() { + // Handle extension detection + this.bus?.on('extensionDetected', (extension) => { + console.log('Extension detected'); + LiteState.extensionBridge.foundExtension = extension; + }); + + // Handle auth URL from NIP-46 + window.addEventListener('nlAuthUrl', (event) => { + console.log('Auth URL received:', event.detail.url); + // Could show URL in modal or trigger external flow + }); + + // Handle logout events + window.addEventListener('nlLogout', () => { + console.log('Logout event received'); + this.logout(); + }); + } + + static _handleMethodSelected(detail) { + console.log('Method selected:', detail); + + const { method, pubkey, secret, extension } = detail; + + switch (method) { + case 'local': + if (secret && pubkey) { + // Set up local key authentication + const info = { + pubkey, + signer: { method: 'local', secret } + }; + Store.setCurrent(info); + LiteState.bus?.emit('authStateUpdate', info); + this._dispatchAuthEvent('login', info); + } + break; + + case 'extension': + if (pubkey && extension) { + // Store the extension object in the ExtensionBridge for future use + LiteState.extensionBridge.foundExtension = extension; + LiteState.extensionBridge.originalNostr = extension; + + // Set up extension authentication + const info = { + pubkey, + signer: { method: 'extension' } + }; + Store.setCurrent(info); + LiteState.bus?.emit('authStateUpdate', info); + this._dispatchAuthEvent('login', info); + + console.log('Extension authentication set up successfully'); + } else { + // Fallback to extension bridge detection + LiteState.bus?.emit('authMethodSelected', { method: 'extension' }); + } + break; + + case 'readonly': + // Set read-only mode + const readonlyInfo = { + pubkey: '', + signer: { method: 'readonly' } + }; + Store.setCurrent(readonlyInfo); + LiteState.bus?.emit('authStateUpdate', readonlyInfo); + this._dispatchAuthEvent('login', readonlyInfo); + break; + + case 'nip46': + if (secret && pubkey) { + // Set up NIP-46 remote signing + const info = { + pubkey, + signer: { method: 'nip46', ...secret } + }; + Store.setCurrent(info); + LiteState.bus?.emit('authStateUpdate', info); + this._dispatchAuthEvent('login', info); + } + break; + + default: + console.warn('Unhandled auth method:', method); + } + } + + static _dispatchAuthEvent(type, info) { + const eventPayload = { + type, + info, + pubkey: info?.pubkey || '', + method: info?.signer?.method || '', + ...info + }; + + // Dispatch the event + window.dispatchEvent(new CustomEvent('nlAuth', { detail: eventPayload })); + + this.bus?.emit('nlAuth', eventPayload); + } + + static launch(startScreen) { + if (!LiteState.initialized) { + throw new Error('NOSTR_LOGIN_LITE not initialized. Call init() first.'); + } + + console.log('Launch requested with screen:', startScreen); + + // Initialize modal if needed + if (!LiteState.modal) { + // Import modal lazily + if (typeof Modal !== 'undefined') { + LiteState.modal = Modal.init(LiteState.options); + } else { + console.error('Modal component not available'); + return; + } + } + + // Open modal with specified screen + LiteState.modal.open({ startScreen }); + } + + static logout() { + if (!LiteState.initialized) return; + + // Clear current account and state + Store.removeCurrentAccount(); + + // Reset internal state + LiteState.auth = null; + + // Emit logout event + window.dispatchEvent(new CustomEvent('nlLogout')); + LiteState.bus?.emit('logout'); + + console.log('Logged out'); + } + + static setDarkMode(dark) { + if (!LiteState.options) return; + + LiteState.options.darkMode = dark; + Store.setItem('darkMode', dark.toString()); + + // Update modal theme if initialized + if (LiteState.modal) { + // LiteState.modal.updateTheme(); + } + + window.dispatchEvent(new CustomEvent('nlDarkMode', { detail: { dark } })); + } + + static setAuth(o) { + if (!o || !o.type) return; + + console.log('setAuth called:', o); + + // Validate request + if (!['login', 'signup', 'logout'].includes(o.type)) { + throw new Error(`Invalid auth type: ${o.type}`); + } + + if (['login', 'signup'].includes(o.type) && !['connect', 'extension', 'local', 'otp', 'readOnly'].includes(o.method)) { + throw new Error(`Invalid auth method: ${o.method}`); + } + + // Handle based on type + switch (o.type) { + case 'logout': + this.logout(); + break; + default: + // Delegate to auth system - will be implemented + console.log('Auth delegation not yet implemented'); + } + } + + static cancelNeedAuth() { + // Cancel any ongoing auth flows + LiteState.bus?.emit('cancelAuth'); + console.log('Auth flow cancelled'); + } +} + +// Initialize the window.nostr facade +const windowNostr = { + async getPublicKey() { + if (!LiteState.initialized) { + throw new Error('NOSTR_LOGIN_LITE not initialized'); + } + + const current = Store.getCurrent(); + if (current && current.pubkey) { + return current.pubkey; + } + + // Trigger auth flow + const authPromise = new Promise((resolve, reject) => { + const handleAuth = (event) => { + window.removeEventListener('nlAuth', handleAuth); + if (event.detail.type === 'login' && event.detail.pubkey) { + resolve(event.detail.pubkey); + } else { + reject(new Error('Authentication cancelled')); + } + }; + + window.addEventListener('nlAuth', handleAuth); + + // Set timeout + setTimeout(() => { + window.removeEventListener('nlAuth', handleAuth); + reject(new Error('Authentication timeout')); + }, 300000); // 5 minutes + }); + + // Launch auth modal + NostrLite.launch('login'); + + return authPromise; + }, + + async signEvent(event) { + if (!LiteState.initialized) { + throw new Error('NOSTR_LOGIN_LITE not initialized'); + } + + let current = Store.getCurrent(); + + // If no current account, trigger auth + if (!current) { + await window.nostr.getPublicKey(); // This will trigger auth + current = Store.getCurrent(); + if (!current) { + throw new Error('Authentication failed'); + } + } + + // Route to appropriate signer + if (current.signer?.method === 'local' && current.signer.secret) { + const signer = new LocalSigner(this._hexToUint8Array(current.signer.secret)); + return await signer.sign(event); + } else if (current.signer?.method === 'nip46' && current.signer.bunkerSigner) { + // Route to NIP-46 remote signer + try { + const bunkerSigner = current.signer.bunkerSigner; + const signedEvent = await bunkerSigner.signEvent(event); + return signedEvent; + } catch (error) { + console.error('NIP-46 signEvent failed:', error); + throw new Error(`NIP-46 signing failed: ${error.message}`); + } + } else if (current.signer?.method === 'readonly') { + throw new Error('Cannot sign events in read-only mode'); + } else if (current.signer?.method === 'extension' && LiteState.extensionBridge?.hasExtension()) { + // Route to extension + const temp = window.nostr; + window.nostr = LiteState.extensionBridge.foundExtension; + try { + const signedEvent = await window.nostr.signEvent(event); + return signedEvent; + } finally { + window.nostr = temp; + } + } + + throw new Error('No suitable signer available for current account'); + }, + + _hexToUint8Array(hex) { + // Convert hex string to Uint8Array + const bytes = new Uint8Array(hex.length / 2); + for (let i = 0; i < bytes.length; i++) { + bytes[i] = parseInt(hex.substr(i * 2, 2), 16); + } + return bytes; + }, + + nip04: { + async encrypt(pubkey, plaintext) { + if (!LiteState.initialized) { + throw new Error('NOSTR_LOGIN_LITE not initialized'); + } + + const current = Store.getCurrent(); + if (!current) { + throw new Error('No authenticated user'); + } + + if (current.signer?.method === 'nip46' && current.signer.bunkerSigner) { + // Route to NIP-46 remote signer + try { + const bunkerSigner = current.signer.bunkerSigner; + return await bunkerSigner.nip04Encrypt(pubkey, plaintext); + } catch (error) { + console.error('NIP-46 nip04 encrypt failed:', error); + throw new Error(`NIP-46 encrypting failed: ${error.message}`); + } + } else if (current.signer?.method === 'local' && current.signer.secret) { + const signer = new LocalSigner(current.signer.secret); + return await signer.encrypt04(pubkey, plaintext); + } else if (current.signer?.method === 'extension' && LiteState.extensionBridge?.hasExtension()) { + const temp = window.nostr; + window.nostr = LiteState.extensionBridge.foundExtension; + try { + return await window.nostr.nip04.encrypt(pubkey, plaintext); + } finally { + window.nostr = temp; + } + } + + throw new Error('No suitable signer available for NIP-04 encryption'); + }, + + async decrypt(pubkey, ciphertext) { + if (!LiteState.initialized) { + throw new Error('NOSTR_LOGIN_LITE not initialized'); + } + + const current = Store.getCurrent(); + if (!current) { + throw new Error('No authenticated user'); + } + + if (current.signer?.method === 'nip46' && current.signer.bunkerSigner) { + // Route to NIP-46 remote signer + try { + const bunkerSigner = current.signer.bunkerSigner; + return await bunkerSigner.nip04Decrypt(pubkey, ciphertext); + } catch (error) { + console.error('NIP-46 nip04 decrypt failed:', error); + throw new Error(`NIP-46 decrypting failed: ${error.message}`); + } + } else if (current.signer?.method === 'local' && current.signer.secret) { + const signer = new LocalSigner(current.signer.secret); + return await signer.decrypt04(pubkey, ciphertext); + } else if (current.signer?.method === 'extension' && LiteState.extensionBridge?.hasExtension()) { + const temp = window.nostr; + window.nostr = LiteState.extensionBridge.foundExtension; + try { + return await window.nostr.nip04.decrypt(pubkey, ciphertext); + } finally { + window.nostr = temp; + } + } + + throw new Error('No suitable signer available for NIP-04 decryption'); + } + }, + + nip44: { + async encrypt(pubkey, plaintext) { + if (!LiteState.initialized) { + throw new Error('NOSTR_LOGIN_LITE not initialized'); + } + + const current = Store.getCurrent(); + if (!current) { + throw new Error('No authenticated user'); + } + + if (current.signer?.method === 'nip46' && current.signer.bunkerSigner) { + // Route to NIP-46 remote signer + try { + const bunkerSigner = current.signer.bunkerSigner; + return await bunkerSigner.nip44Encrypt(pubkey, plaintext); + } catch (error) { + console.error('NIP-46 nip44 encrypt failed:', error); + throw new Error(`NIP-46 encrypting failed: ${error.message}`); + } + } else if (current.signer?.method === 'local' && current.signer.secret) { + const signer = new LocalSigner(current.signer.secret); + return await signer.encrypt44(pubkey, plaintext); + } else if (current.signer?.method === 'extension' && LiteState.extensionBridge?.hasExtension()) { + // Use extension if it supports nip44 + const temp = window.nostr; + window.nostr = LiteState.extensionBridge.foundExtension; + try { + if (window.nostr.nip44) { + return await window.nostr.nip44.encrypt(pubkey, plaintext); + } else { + throw new Error('Extension does not support NIP-44'); + } + } finally { + window.nostr = temp; + } + } + + throw new Error('No suitable signer available for NIP-44 encryption'); + }, + + async decrypt(pubkey, ciphertext) { + if (!LiteState.initialized) { + throw new Error('NOSTR_LOGIN_LITE not initialized'); + } + + const current = Store.getCurrent(); + if (!current) { + throw new Error('No authenticated user'); + } + + if (current.signer?.method === 'nip46' && current.signer.bunkerSigner) { + // Route to NIP-46 remote signer + try { + const bunkerSigner = current.signer.bunkerSigner; + return await bunkerSigner.nip44Decrypt(pubkey, ciphertext); + } catch (error) { + console.error('NIP-46 nip44 decrypt failed:', error); + throw new Error(`NIP-46 decrypting failed: ${error.message}`); + } + } else if (current.signer?.method === 'local' && current.signer.secret) { + const signer = new LocalSigner(current.signer.secret); + return await signer.decrypt44(pubkey, ciphertext); + } else if (current.signer?.method === 'extension' && LiteState.extensionBridge?.hasExtension()) { + const temp = window.nostr; + window.nostr = LiteState.extensionBridge.foundExtension; + try { + if (window.nostr.nip44) { + return await window.nostr.nip44.decrypt(pubkey, ciphertext); + } else { + throw new Error('Extension does not support NIP-44'); + } + } finally { + window.nostr = temp; + } + } + + throw new Error('No suitable signer available for NIP-44 decryption'); + } + } +}; + +// Export the API +window.NOSTR_LOGIN_LITE = { + init: NostrLite.init.bind(NostrLite), + launch: NostrLite.launch.bind(NostrLite), + logout: NostrLite.logout.bind(NostrLite), + setDarkMode: NostrLite.setDarkMode.bind(NostrLite), + setAuth: NostrLite.setAuth.bind(NostrLite), + cancelNeedAuth: NostrLite.cancelNeedAuth.bind(NostrLite), + // Expose internal components for debugging + get _extensionBridge() { + return LiteState.extensionBridge; + }, + get _state() { + return LiteState; + } +}; + +// Set window.nostr facade properly (extensions will be handled by ExtensionBridge) +if (typeof window !== 'undefined') { + window.nostr = windowNostr; + + // Ensure all methods are properly exposed + console.log('NOSTR_LOGIN_LITE: window.nostr facade installed with methods:', Object.keys(windowNostr)); +} + +console.log('NOSTR_LOGIN_LITE loaded - use window.NOSTR_LOGIN_LITE.init(options) to initialize'); + diff --git a/lite/nostr-login-lite.js b/lite/nostr-login-lite.js new file mode 100644 index 0000000..7ce1839 --- /dev/null +++ b/lite/nostr-login-lite.js @@ -0,0 +1,918 @@ +/** + * NOSTR_LOGIN_LITE + * A minimal, dependency-light replacement for the current auth/UI stack + * Preserves all login methods and window.nostr surface + */ + +// Import NIP-46 client +if (typeof NIP46Client === 'undefined') { + // Load NIP46Client if not already available (for non-bundled version) + const script = document.createElement('script'); + script.src = './core/nip46-client.js'; + document.head.appendChild(script); +} + +// Global state +const LiteState = { + initialized: false, + windowNostr: null, + options: null, + auth: null, + modal: null, + bus: null, + pool: null, + nip44Codec: null, + extensionBridge: null, + nip46Client: null +}; + +// Dependencies verification +class Deps { + static ensureNostrToolsLoaded() { + if (typeof window === 'undefined') { + throw new Error('NOSTR_LOGIN_LITE must run in browser environment'); + } + + if (!window.NostrTools) { + throw new Error( + 'window.NostrTools is required but not loaded. ' + + 'Please include: ' + ); + } + + // Verify required APIs + const required = ['SimplePool', 'getPublicKey', 'finalizeEvent', 'nip04']; + for (const api of required) { + if (!window.NostrTools[api]) { + throw new Error(`window.NostrTools.${api} is required but missing`); + } + } + + // Check for key generation function (might be generateSecretKey or generatePrivateKey) + if (!window.NostrTools.generateSecretKey && !window.NostrTools.generatePrivateKey) { + throw new Error('window.NostrTools must have either generateSecretKey or generatePrivateKey'); + } + + return true; + } +} + +// Event Bus for internal communication +class Bus { + constructor() { + this.handlers = {}; + } + + on(event, handler) { + if (!this.handlers[event]) { + this.handlers[event] = []; + } + this.handlers[event].push(handler); + } + + off(event, handler) { + if (!this.handlers[event]) return; + this.handlers[event] = this.handlers[event].filter(h => h !== handler); + } + + emit(event, payload) { + if (!this.handlers[event]) return; + this.handlers[event].forEach(handler => { + try { + handler(payload); + } catch (e) { + console.error(`Error in event handler for ${event}:`, e); + } + }); + } +} + +// Storage helpers +class Store { + static addAccount(info) { + const accounts = this.getAccounts(); + // Remove existing account with same pubkey if present + const filtered = accounts.filter(acc => acc.pubkey !== info.pubkey); + filtered.push(info); + localStorage.setItem('nl_accounts', JSON.stringify(filtered)); + } + + static removeCurrentAccount() { + const current = this.getCurrent(); + if (current && current.pubkey) { + const accounts = this.getAccounts(); + const filtered = accounts.filter(acc => acc.pubkey !== current.pubkey); + localStorage.setItem('nl_accounts', JSON.stringify(filtered)); + localStorage.removeItem('nl_current'); + } + } + + static getCurrent() { + try { + const stored = localStorage.getItem('nl_current'); + return stored ? JSON.parse(stored) : null; + } catch (e) { + console.error('Error parsing current account:', e); + return null; + } + } + + static setCurrent(info) { + localStorage.setItem('nl_current', JSON.stringify(info)); + } + + static getAccounts() { + try { + const stored = localStorage.getItem('nl_accounts'); + return stored ? JSON.parse(stored) : []; + } catch (e) { + console.error('Error parsing accounts:', e); + return []; + } + } + + static getRecents() { + // Return last 5 used accounts in reverse chronological order + const accounts = this.getAccounts().slice(-5).reverse(); + return accounts; + } + + static setItem(key, value) { + localStorage.setItem(`nl-${key}`, value); + } + + static getItem(key) { + return localStorage.getItem(`nl-${key}`); + } + + static async getIcon() { + // Simple default icon - could be extended to fetch from profile + return '🔑'; + } +} + +// Relay configuration helpers +class Relays { + static getDefaultRelays(options) { + if (options?.relays) { + return this.normalize(options.relays); + } + + // Default relays for fallbacks + return [ + 'wss://relay.damus.io', + 'wss://relay.snort.social', + 'wss://nos.lol' + ]; + } + + static normalize(relays) { + return relays.map(relay => { + // Ensure wss:// prefix + if (relay.startsWith('ws://')) { + return relay.replace('ws://', 'wss://'); + } else if (!relay.startsWith('wss://')) { + return `wss://${relay}`; + } + return relay; + }).filter(relay => { + // Remove duplicates and validate URLs + try { + new URL(relay); + return true; + } catch { + return false; + } + }).filter((relay, index, self) => self.indexOf(relay) === index); // dedupe + } +} + +// Minimal NIP-44 codec fallback +class Nip44 { + constructor() { + this.Nip44 = null; + // Initialize with existing codec if available + this.nip44Available = window.NostrTools?.nip44; + } + + static encrypt(ourSk, theirPk, plaintext) { + if (window.NostrTools?.nip44?.encrypt) { + return window.NostrTools.nip44.encrypt(ourSk, theirPk, plaintext); + } + + throw new Error('NIP-44 encryption not available. Please use nostr-tools@>=2.x or provide codec implementation.'); + } + + static decrypt(ourSk, theirPk, ciphertext) { + if (window.NostrTools?.nip44?.decrypt) { + return window.NostrTools.nip44.decrypt(ourSk, theirPk, ciphertext); + } + + throw new Error('NIP-44 decryption not available. Please use nostr-tools@>=2.x or provide codec implementation.'); + } +} + +// LocalSigner wrapping window.NostrTools +class LocalSigner { + constructor(sk) { + this.sk = sk; + // Generate pubkey from secret key + this.pk = this._getPubKey(); + } + + _getPubKey() { + const seckey = this.sk.startsWith('nsec') ? + window.NostrTools.nip19.decode(this.sk).data : + this.sk; + return window.NostrTools.getPublicKey(seckey); + } + + pubkey() { + return this.pk; + } + + async sign(event) { + // Prepare event for signing + const ev = { ...event }; + ev.pubkey = this.pk; + + // Generate event ID and sign + const signedEvent = await window.NostrTools.finalizeEvent(ev, this.sk); + return signedEvent; + } + + async encrypt04(pubkey, plaintext) { + return await window.NostrTools.nip04.encrypt(this.sk, pubkey, plaintext); + } + + async decrypt04(pubkey, ciphertext) { + return await window.NostrTools.nip04.decrypt(this.sk, pubkey, ciphertext); + } + + async encrypt44(pubkey, plaintext) { + return Nip44.encrypt(this.sk, pubkey, plaintext); + } + + async decrypt44(pubkey, ciphertext) { + return Nip44.decrypt(this.sk, pubkey, ciphertext); + } +} + +// ExtensionBridge for detecting and managing browser extensions +class ExtensionBridge { + constructor() { + this.checking = false; + this.checkInterval = null; + this.originalNostr = null; + this.foundExtension = null; + } + + startChecking(nostrLite) { + if (this.checking) return; + this.checking = true; + + const check = () => { + this.initExtension(nostrLite); + }; + + // Check immediately + check(); + + // Then check every 200ms for 30 seconds + this.checkInterval = setInterval(check, 200); + + // Stop checking after 30 seconds + setTimeout(() => { + clearInterval(this.checkInterval); + this.checkInterval = null; + }, 30000); + } + + initExtension(nostrLite, lastTry = false) { + const extension = window.nostr; + + if (extension && !this.foundExtension) { + // Check if this is actually a real extension, not our own library + const isRealExtension = ( + extension !== nostrLite && // Not the same object we're about to assign + extension !== windowNostr && // Not our windowNostr object + typeof extension._hexToUint8Array !== 'function' && // Our library has this internal method + extension.constructor.name !== 'Object' // Real extensions usually have proper constructors + ); + + if (isRealExtension) { + this.foundExtension = extension; + + // Cache the extension and reassign window.nostr to our lite version + this.originalNostr = window.nostr; + window.nostr = nostrLite; + + console.log('Real Nostr extension detected and bridged:', extension.constructor.name); + + // If currently authenticated, reconcile state + if (LiteState.auth?.signer?.method === 'extension') { + this.reconcileExtension(); + } + } else { + console.log('Skipping non-extension object on window.nostr:', extension.constructor.name); + } + } + } + + hasExtension() { + return !!this.foundExtension; + } + + async setExtensionReadPubkey(expectedPubkey = null) { + if (!this.foundExtension) return false; + + try { + // Temporarily set window.nostr to extension + const temp = window.nostr; + window.nostr = this.foundExtension; + + const pubkey = await this.foundExtension.getPublicKey(); + + // Restore our lite implementation + window.nostr = temp; + + if (expectedPubkey && pubkey !== expectedPubkey) { + console.warn(`Extension pubkey ${pubkey} does not match expected ${expectedPubkey}`); + } + + return pubkey; + } catch (e) { + console.error('Error reading extension pubkey:', e); + return null; + } + } + + trySetForPubkey(expectedPubkey) { + if (!this.hasExtension()) return false; + + this.setExtensionReadPubkey(expectedPubkey).then(pubkey => { + if (pubkey) { + LiteState.bus?.emit('extensionLogin', { pubkey }); + } + }); + + return true; + } + + setExtension() { + if (!this.foundExtension) return; + window.nostr = this.foundExtension; + this.setExtensionReadPubkey().then(pubkey => { + if (pubkey) { + LiteState.bus?.emit('extensionSet', { pubkey }); + } + }); + } + + unset(nostrLite) { + window.nostr = nostrLite; + } + + reconcileExtension() { + // Handle extension state changes + this.setExtensionReadPubkey().then(pubkey => { + if (pubkey) { + // Update current account if extension is the signer + const current = Store.getCurrent(); + if (current && current.signer?.method === 'extension') { + const info = { + ...current, + pubkey, + signer: { method: 'extension' } + }; + Store.setCurrent(info); + LiteState.bus?.emit('authStateUpdate', info); + } + } + }); + } +} + +// Main API surface +class NostrLite { + static async init(options = {}) { + // Ensure dependencies are loaded + Deps.ensureNostrToolsLoaded(); + + // Prevent double initialization + if (LiteState.initialized) { + console.warn('NOSTR_LOGIN_LITE already initialized'); + return; + } + + // Initialize components + LiteState.bus = new Bus(); + LiteState.extensionBridge = new ExtensionBridge(); + + // Initialize NIP-46 client + LiteState.nip46Client = new NIP46Client(); + + // Store options + LiteState.options = { + theme: 'light', + darkMode: false, + relays: Relays.getDefaultRelays(options), + methods: { + connect: true, + extension: true, + local: true, + readonly: true, + otp: true + }, + otp: {}, + ...options + }; + + // Start extension detection + LiteState.extensionBridge.startChecking(windowNostr); + + // Setup auth methods + this._setupAuth(); + + // Initialize modal UI + // this._initModal(); + + console.log('NOSTR_LOGIN_LITE initialized with options:', LiteState.options); + LiteState.initialized = true; + } + + static _setupAuth() { + // Set up event listeners for modal interactions + window.addEventListener('nlMethodSelected', (event) => { + this._handleMethodSelected(event.detail); + }); + + // Set up other auth-related event listeners + this._setupAuthEventListeners(); + + console.log('Auth system setup loaded'); + } + + static _setupAuthEventListeners() { + // Handle extension detection + this.bus?.on('extensionDetected', (extension) => { + console.log('Extension detected'); + LiteState.extensionBridge.foundExtension = extension; + }); + + // Handle auth URL from NIP-46 + window.addEventListener('nlAuthUrl', (event) => { + console.log('Auth URL received:', event.detail.url); + // Could show URL in modal or trigger external flow + }); + + // Handle logout events + window.addEventListener('nlLogout', () => { + console.log('Logout event received'); + this.logout(); + }); + } + + static _handleMethodSelected(detail) { + console.log('Method selected:', detail); + + const { method, pubkey, secret, extension } = detail; + + switch (method) { + case 'local': + if (secret && pubkey) { + // Set up local key authentication + const info = { + pubkey, + signer: { method: 'local', secret } + }; + Store.setCurrent(info); + LiteState.bus?.emit('authStateUpdate', info); + this._dispatchAuthEvent('login', info); + } + break; + + case 'extension': + if (pubkey && extension) { + // Store the extension object in the ExtensionBridge for future use + LiteState.extensionBridge.foundExtension = extension; + LiteState.extensionBridge.originalNostr = extension; + + // Set up extension authentication + const info = { + pubkey, + signer: { method: 'extension' } + }; + Store.setCurrent(info); + LiteState.bus?.emit('authStateUpdate', info); + this._dispatchAuthEvent('login', info); + + console.log('Extension authentication set up successfully'); + } else { + // Fallback to extension bridge detection + LiteState.bus?.emit('authMethodSelected', { method: 'extension' }); + } + break; + + case 'readonly': + // Set read-only mode + const readonlyInfo = { + pubkey: '', + signer: { method: 'readonly' } + }; + Store.setCurrent(readonlyInfo); + LiteState.bus?.emit('authStateUpdate', readonlyInfo); + this._dispatchAuthEvent('login', readonlyInfo); + break; + + case 'nip46': + if (secret && pubkey) { + // Set up NIP-46 remote signing + const info = { + pubkey, + signer: { method: 'nip46', ...secret } + }; + Store.setCurrent(info); + LiteState.bus?.emit('authStateUpdate', info); + this._dispatchAuthEvent('login', info); + } + break; + + default: + console.warn('Unhandled auth method:', method); + } + } + + static _dispatchAuthEvent(type, info) { + const eventPayload = { + type, + info, + pubkey: info?.pubkey || '', + method: info?.signer?.method || '', + ...info + }; + + // Dispatch the event + window.dispatchEvent(new CustomEvent('nlAuth', { detail: eventPayload })); + + this.bus?.emit('nlAuth', eventPayload); + } + + static launch(startScreen) { + if (!LiteState.initialized) { + throw new Error('NOSTR_LOGIN_LITE not initialized. Call init() first.'); + } + + console.log('Launch requested with screen:', startScreen); + + // Initialize modal if needed + if (!LiteState.modal) { + // Import modal lazily + if (typeof Modal !== 'undefined') { + LiteState.modal = Modal.init(LiteState.options); + } else { + console.error('Modal component not available'); + return; + } + } + + // Open modal with specified screen + LiteState.modal.open({ startScreen }); + } + + static logout() { + if (!LiteState.initialized) return; + + // Clear current account and state + Store.removeCurrentAccount(); + + // Reset internal state + LiteState.auth = null; + + // Emit logout event + window.dispatchEvent(new CustomEvent('nlLogout')); + LiteState.bus?.emit('logout'); + + console.log('Logged out'); + } + + static setDarkMode(dark) { + if (!LiteState.options) return; + + LiteState.options.darkMode = dark; + Store.setItem('darkMode', dark.toString()); + + // Update modal theme if initialized + if (LiteState.modal) { + // LiteState.modal.updateTheme(); + } + + window.dispatchEvent(new CustomEvent('nlDarkMode', { detail: { dark } })); + } + + static setAuth(o) { + if (!o || !o.type) return; + + console.log('setAuth called:', o); + + // Validate request + if (!['login', 'signup', 'logout'].includes(o.type)) { + throw new Error(`Invalid auth type: ${o.type}`); + } + + if (['login', 'signup'].includes(o.type) && !['connect', 'extension', 'local', 'otp', 'readOnly'].includes(o.method)) { + throw new Error(`Invalid auth method: ${o.method}`); + } + + // Handle based on type + switch (o.type) { + case 'logout': + this.logout(); + break; + default: + // Delegate to auth system - will be implemented + console.log('Auth delegation not yet implemented'); + } + } + + static cancelNeedAuth() { + // Cancel any ongoing auth flows + LiteState.bus?.emit('cancelAuth'); + console.log('Auth flow cancelled'); + } +} + +// Initialize the window.nostr facade +const windowNostr = { + async getPublicKey() { + if (!LiteState.initialized) { + throw new Error('NOSTR_LOGIN_LITE not initialized'); + } + + const current = Store.getCurrent(); + if (current && current.pubkey) { + return current.pubkey; + } + + // Trigger auth flow + const authPromise = new Promise((resolve, reject) => { + const handleAuth = (event) => { + window.removeEventListener('nlAuth', handleAuth); + if (event.detail.type === 'login' && event.detail.pubkey) { + resolve(event.detail.pubkey); + } else { + reject(new Error('Authentication cancelled')); + } + }; + + window.addEventListener('nlAuth', handleAuth); + + // Set timeout + setTimeout(() => { + window.removeEventListener('nlAuth', handleAuth); + reject(new Error('Authentication timeout')); + }, 300000); // 5 minutes + }); + + // Launch auth modal + NostrLite.launch('login'); + + return authPromise; + }, + + async signEvent(event) { + if (!LiteState.initialized) { + throw new Error('NOSTR_LOGIN_LITE not initialized'); + } + + let current = Store.getCurrent(); + + // If no current account, trigger auth + if (!current) { + await window.nostr.getPublicKey(); // This will trigger auth + current = Store.getCurrent(); + if (!current) { + throw new Error('Authentication failed'); + } + } + + // Route to appropriate signer + if (current.signer?.method === 'local' && current.signer.secret) { + const signer = new LocalSigner(this._hexToUint8Array(current.signer.secret)); + return await signer.sign(event); + } else if (current.signer?.method === 'nip46' && current.signer.bunkerSigner) { + // Route to NIP-46 remote signer + try { + const bunkerSigner = current.signer.bunkerSigner; + const signedEvent = await bunkerSigner.signEvent(event); + return signedEvent; + } catch (error) { + console.error('NIP-46 signEvent failed:', error); + throw new Error(`NIP-46 signing failed: ${error.message}`); + } + } else if (current.signer?.method === 'readonly') { + throw new Error('Cannot sign events in read-only mode'); + } else if (current.signer?.method === 'extension' && LiteState.extensionBridge?.hasExtension()) { + // Route to extension + const temp = window.nostr; + window.nostr = LiteState.extensionBridge.foundExtension; + try { + const signedEvent = await window.nostr.signEvent(event); + return signedEvent; + } finally { + window.nostr = temp; + } + } + + throw new Error('No suitable signer available for current account'); + }, + + _hexToUint8Array(hex) { + // Convert hex string to Uint8Array + const bytes = new Uint8Array(hex.length / 2); + for (let i = 0; i < bytes.length; i++) { + bytes[i] = parseInt(hex.substr(i * 2, 2), 16); + } + return bytes; + }, + + nip04: { + async encrypt(pubkey, plaintext) { + if (!LiteState.initialized) { + throw new Error('NOSTR_LOGIN_LITE not initialized'); + } + + const current = Store.getCurrent(); + if (!current) { + throw new Error('No authenticated user'); + } + + if (current.signer?.method === 'nip46' && current.signer.bunkerSigner) { + // Route to NIP-46 remote signer + try { + const bunkerSigner = current.signer.bunkerSigner; + return await bunkerSigner.nip04Encrypt(pubkey, plaintext); + } catch (error) { + console.error('NIP-46 nip04 encrypt failed:', error); + throw new Error(`NIP-46 encrypting failed: ${error.message}`); + } + } else if (current.signer?.method === 'local' && current.signer.secret) { + const signer = new LocalSigner(current.signer.secret); + return await signer.encrypt04(pubkey, plaintext); + } else if (current.signer?.method === 'extension' && LiteState.extensionBridge?.hasExtension()) { + const temp = window.nostr; + window.nostr = LiteState.extensionBridge.foundExtension; + try { + return await window.nostr.nip04.encrypt(pubkey, plaintext); + } finally { + window.nostr = temp; + } + } + + throw new Error('No suitable signer available for NIP-04 encryption'); + }, + + async decrypt(pubkey, ciphertext) { + if (!LiteState.initialized) { + throw new Error('NOSTR_LOGIN_LITE not initialized'); + } + + const current = Store.getCurrent(); + if (!current) { + throw new Error('No authenticated user'); + } + + if (current.signer?.method === 'nip46' && current.signer.bunkerSigner) { + // Route to NIP-46 remote signer + try { + const bunkerSigner = current.signer.bunkerSigner; + return await bunkerSigner.nip04Decrypt(pubkey, ciphertext); + } catch (error) { + console.error('NIP-46 nip04 decrypt failed:', error); + throw new Error(`NIP-46 decrypting failed: ${error.message}`); + } + } else if (current.signer?.method === 'local' && current.signer.secret) { + const signer = new LocalSigner(current.signer.secret); + return await signer.decrypt04(pubkey, ciphertext); + } else if (current.signer?.method === 'extension' && LiteState.extensionBridge?.hasExtension()) { + const temp = window.nostr; + window.nostr = LiteState.extensionBridge.foundExtension; + try { + return await window.nostr.nip04.decrypt(pubkey, ciphertext); + } finally { + window.nostr = temp; + } + } + + throw new Error('No suitable signer available for NIP-04 decryption'); + } + }, + + nip44: { + async encrypt(pubkey, plaintext) { + if (!LiteState.initialized) { + throw new Error('NOSTR_LOGIN_LITE not initialized'); + } + + const current = Store.getCurrent(); + if (!current) { + throw new Error('No authenticated user'); + } + + if (current.signer?.method === 'nip46' && current.signer.bunkerSigner) { + // Route to NIP-46 remote signer + try { + const bunkerSigner = current.signer.bunkerSigner; + return await bunkerSigner.nip44Encrypt(pubkey, plaintext); + } catch (error) { + console.error('NIP-46 nip44 encrypt failed:', error); + throw new Error(`NIP-46 encrypting failed: ${error.message}`); + } + } else if (current.signer?.method === 'local' && current.signer.secret) { + const signer = new LocalSigner(current.signer.secret); + return await signer.encrypt44(pubkey, plaintext); + } else if (current.signer?.method === 'extension' && LiteState.extensionBridge?.hasExtension()) { + // Use extension if it supports nip44 + const temp = window.nostr; + window.nostr = LiteState.extensionBridge.foundExtension; + try { + if (window.nostr.nip44) { + return await window.nostr.nip44.encrypt(pubkey, plaintext); + } else { + throw new Error('Extension does not support NIP-44'); + } + } finally { + window.nostr = temp; + } + } + + throw new Error('No suitable signer available for NIP-44 encryption'); + }, + + async decrypt(pubkey, ciphertext) { + if (!LiteState.initialized) { + throw new Error('NOSTR_LOGIN_LITE not initialized'); + } + + const current = Store.getCurrent(); + if (!current) { + throw new Error('No authenticated user'); + } + + if (current.signer?.method === 'nip46' && current.signer.bunkerSigner) { + // Route to NIP-46 remote signer + try { + const bunkerSigner = current.signer.bunkerSigner; + return await bunkerSigner.nip44Decrypt(pubkey, ciphertext); + } catch (error) { + console.error('NIP-46 nip44 decrypt failed:', error); + throw new Error(`NIP-46 decrypting failed: ${error.message}`); + } + } else if (current.signer?.method === 'local' && current.signer.secret) { + const signer = new LocalSigner(current.signer.secret); + return await signer.decrypt44(pubkey, ciphertext); + } else if (current.signer?.method === 'extension' && LiteState.extensionBridge?.hasExtension()) { + const temp = window.nostr; + window.nostr = LiteState.extensionBridge.foundExtension; + try { + if (window.nostr.nip44) { + return await window.nostr.nip44.decrypt(pubkey, ciphertext); + } else { + throw new Error('Extension does not support NIP-44'); + } + } finally { + window.nostr = temp; + } + } + + throw new Error('No suitable signer available for NIP-44 decryption'); + } + } +}; + +// Export the API +window.NOSTR_LOGIN_LITE = { + init: NostrLite.init.bind(NostrLite), + launch: NostrLite.launch.bind(NostrLite), + logout: NostrLite.logout.bind(NostrLite), + setDarkMode: NostrLite.setDarkMode.bind(NostrLite), + setAuth: NostrLite.setAuth.bind(NostrLite), + cancelNeedAuth: NostrLite.cancelNeedAuth.bind(NostrLite), + // Expose internal components for debugging + get _extensionBridge() { + return LiteState.extensionBridge; + }, + get _state() { + return LiteState; + } +}; + +// Set window.nostr facade properly (extensions will be handled by ExtensionBridge) +if (typeof window !== 'undefined') { + window.nostr = windowNostr; + + // Ensure all methods are properly exposed + console.log('NOSTR_LOGIN_LITE: window.nostr facade installed with methods:', Object.keys(windowNostr)); +} + +console.log('NOSTR_LOGIN_LITE loaded - use window.NOSTR_LOGIN_LITE.init(options) to initialize'); \ No newline at end of file diff --git a/lite/nostr.bundle.js b/lite/nostr.bundle.js new file mode 100644 index 0000000..17634d1 --- /dev/null +++ b/lite/nostr.bundle.js @@ -0,0 +1,6860 @@ +"use strict"; +var NostrTools = (() => { + var __defProp = Object.defineProperty; + var __getOwnPropDesc = Object.getOwnPropertyDescriptor; + var __getOwnPropNames = Object.getOwnPropertyNames; + var __hasOwnProp = Object.prototype.hasOwnProperty; + var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); + }; + var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; + }; + var __toCommonJS = (mod2) => __copyProps(__defProp({}, "__esModule", { value: true }), mod2); + + // index.ts + var nostr_tools_exports = {}; + __export(nostr_tools_exports, { + Relay: () => Relay, + SimplePool: () => SimplePool, + finalizeEvent: () => finalizeEvent, + fj: () => fakejson_exports, + generateSecretKey: () => generateSecretKey, + getEventHash: () => getEventHash, + getFilterLimit: () => getFilterLimit, + getPublicKey: () => getPublicKey, + kinds: () => kinds_exports, + matchFilter: () => matchFilter, + matchFilters: () => matchFilters, + mergeFilters: () => mergeFilters, + nip04: () => nip04_exports, + nip05: () => nip05_exports, + nip10: () => nip10_exports, + nip11: () => nip11_exports, + nip13: () => nip13_exports, + nip17: () => nip17_exports, + nip18: () => nip18_exports, + nip19: () => nip19_exports, + nip21: () => nip21_exports, + nip25: () => nip25_exports, + nip27: () => nip27_exports, + nip28: () => nip28_exports, + nip30: () => nip30_exports, + nip39: () => nip39_exports, + nip42: () => nip42_exports, + nip44: () => nip44_exports, + nip47: () => nip47_exports, + nip54: () => nip54_exports, + nip57: () => nip57_exports, + nip59: () => nip59_exports, + nip98: () => nip98_exports, + parseReferences: () => parseReferences, + serializeEvent: () => serializeEvent, + sortEvents: () => sortEvents, + utils: () => utils_exports2, + validateEvent: () => validateEvent, + verifiedSymbol: () => verifiedSymbol, + verifyEvent: () => verifyEvent + }); + + // node_modules/@noble/curves/node_modules/@noble/hashes/esm/_assert.js + function number(n) { + if (!Number.isSafeInteger(n) || n < 0) + throw new Error(`Wrong positive integer: ${n}`); + } + function bytes(b, ...lengths) { + if (!(b instanceof Uint8Array)) + throw new Error("Expected Uint8Array"); + if (lengths.length > 0 && !lengths.includes(b.length)) + throw new Error(`Expected Uint8Array of length ${lengths}, not of length=${b.length}`); + } + function hash(hash3) { + if (typeof hash3 !== "function" || typeof hash3.create !== "function") + throw new Error("Hash should be wrapped by utils.wrapConstructor"); + number(hash3.outputLen); + number(hash3.blockLen); + } + function exists(instance, checkFinished = true) { + if (instance.destroyed) + throw new Error("Hash instance has been destroyed"); + if (checkFinished && instance.finished) + throw new Error("Hash#digest() has already been called"); + } + function output(out, instance) { + bytes(out); + const min = instance.outputLen; + if (out.length < min) { + throw new Error(`digestInto() expects output buffer of length at least ${min}`); + } + } + + // node_modules/@noble/curves/node_modules/@noble/hashes/esm/crypto.js + var crypto = typeof globalThis === "object" && "crypto" in globalThis ? globalThis.crypto : void 0; + + // node_modules/@noble/curves/node_modules/@noble/hashes/esm/utils.js + var u8a = (a) => a instanceof Uint8Array; + var createView = (arr) => new DataView(arr.buffer, arr.byteOffset, arr.byteLength); + var rotr = (word, shift) => word << 32 - shift | word >>> shift; + var isLE = new Uint8Array(new Uint32Array([287454020]).buffer)[0] === 68; + if (!isLE) + throw new Error("Non little-endian hardware is not supported"); + function utf8ToBytes(str) { + if (typeof str !== "string") + throw new Error(`utf8ToBytes expected string, got ${typeof str}`); + return new Uint8Array(new TextEncoder().encode(str)); + } + function toBytes(data) { + if (typeof data === "string") + data = utf8ToBytes(data); + if (!u8a(data)) + throw new Error(`expected Uint8Array, got ${typeof data}`); + return data; + } + function concatBytes(...arrays) { + const r = new Uint8Array(arrays.reduce((sum, a) => sum + a.length, 0)); + let pad2 = 0; + arrays.forEach((a) => { + if (!u8a(a)) + throw new Error("Uint8Array expected"); + r.set(a, pad2); + pad2 += a.length; + }); + return r; + } + var Hash = class { + clone() { + return this._cloneInto(); + } + }; + var toStr = {}.toString; + function wrapConstructor(hashCons) { + const hashC = (msg) => hashCons().update(toBytes(msg)).digest(); + const tmp = hashCons(); + hashC.outputLen = tmp.outputLen; + hashC.blockLen = tmp.blockLen; + hashC.create = () => hashCons(); + return hashC; + } + function randomBytes(bytesLength = 32) { + if (crypto && typeof crypto.getRandomValues === "function") { + return crypto.getRandomValues(new Uint8Array(bytesLength)); + } + throw new Error("crypto.getRandomValues must be defined"); + } + + // node_modules/@noble/curves/node_modules/@noble/hashes/esm/_sha2.js + function setBigUint64(view, byteOffset, value, isLE4) { + if (typeof view.setBigUint64 === "function") + return view.setBigUint64(byteOffset, value, isLE4); + const _32n = BigInt(32); + const _u32_max = BigInt(4294967295); + const wh = Number(value >> _32n & _u32_max); + const wl = Number(value & _u32_max); + const h = isLE4 ? 4 : 0; + const l = isLE4 ? 0 : 4; + view.setUint32(byteOffset + h, wh, isLE4); + view.setUint32(byteOffset + l, wl, isLE4); + } + var SHA2 = class extends Hash { + constructor(blockLen, outputLen, padOffset, isLE4) { + super(); + this.blockLen = blockLen; + this.outputLen = outputLen; + this.padOffset = padOffset; + this.isLE = isLE4; + this.finished = false; + this.length = 0; + this.pos = 0; + this.destroyed = false; + this.buffer = new Uint8Array(blockLen); + this.view = createView(this.buffer); + } + update(data) { + exists(this); + const { view, buffer, blockLen } = this; + data = toBytes(data); + const len = data.length; + for (let pos = 0; pos < len; ) { + const take = Math.min(blockLen - this.pos, len - pos); + if (take === blockLen) { + const dataView = createView(data); + for (; blockLen <= len - pos; pos += blockLen) + this.process(dataView, pos); + continue; + } + buffer.set(data.subarray(pos, pos + take), this.pos); + this.pos += take; + pos += take; + if (this.pos === blockLen) { + this.process(view, 0); + this.pos = 0; + } + } + this.length += data.length; + this.roundClean(); + return this; + } + digestInto(out) { + exists(this); + output(out, this); + this.finished = true; + const { buffer, view, blockLen, isLE: isLE4 } = this; + let { pos } = this; + buffer[pos++] = 128; + this.buffer.subarray(pos).fill(0); + if (this.padOffset > blockLen - pos) { + this.process(view, 0); + pos = 0; + } + for (let i2 = pos; i2 < blockLen; i2++) + buffer[i2] = 0; + setBigUint64(view, blockLen - 8, BigInt(this.length * 8), isLE4); + this.process(view, 0); + const oview = createView(out); + const len = this.outputLen; + if (len % 4) + throw new Error("_sha2: outputLen should be aligned to 32bit"); + const outLen = len / 4; + const state = this.get(); + if (outLen > state.length) + throw new Error("_sha2: outputLen bigger than state"); + for (let i2 = 0; i2 < outLen; i2++) + oview.setUint32(4 * i2, state[i2], isLE4); + } + digest() { + const { buffer, outputLen } = this; + this.digestInto(buffer); + const res = buffer.slice(0, outputLen); + this.destroy(); + return res; + } + _cloneInto(to) { + to || (to = new this.constructor()); + to.set(...this.get()); + const { blockLen, buffer, length, finished, destroyed, pos } = this; + to.length = length; + to.pos = pos; + to.finished = finished; + to.destroyed = destroyed; + if (length % blockLen) + to.buffer.set(buffer); + return to; + } + }; + + // node_modules/@noble/curves/node_modules/@noble/hashes/esm/sha256.js + var Chi = (a, b, c) => a & b ^ ~a & c; + var Maj = (a, b, c) => a & b ^ a & c ^ b & c; + var SHA256_K = /* @__PURE__ */ new Uint32Array([ + 1116352408, + 1899447441, + 3049323471, + 3921009573, + 961987163, + 1508970993, + 2453635748, + 2870763221, + 3624381080, + 310598401, + 607225278, + 1426881987, + 1925078388, + 2162078206, + 2614888103, + 3248222580, + 3835390401, + 4022224774, + 264347078, + 604807628, + 770255983, + 1249150122, + 1555081692, + 1996064986, + 2554220882, + 2821834349, + 2952996808, + 3210313671, + 3336571891, + 3584528711, + 113926993, + 338241895, + 666307205, + 773529912, + 1294757372, + 1396182291, + 1695183700, + 1986661051, + 2177026350, + 2456956037, + 2730485921, + 2820302411, + 3259730800, + 3345764771, + 3516065817, + 3600352804, + 4094571909, + 275423344, + 430227734, + 506948616, + 659060556, + 883997877, + 958139571, + 1322822218, + 1537002063, + 1747873779, + 1955562222, + 2024104815, + 2227730452, + 2361852424, + 2428436474, + 2756734187, + 3204031479, + 3329325298 + ]); + var IV = /* @__PURE__ */ new Uint32Array([ + 1779033703, + 3144134277, + 1013904242, + 2773480762, + 1359893119, + 2600822924, + 528734635, + 1541459225 + ]); + var SHA256_W = /* @__PURE__ */ new Uint32Array(64); + var SHA256 = class extends SHA2 { + constructor() { + super(64, 32, 8, false); + this.A = IV[0] | 0; + this.B = IV[1] | 0; + this.C = IV[2] | 0; + this.D = IV[3] | 0; + this.E = IV[4] | 0; + this.F = IV[5] | 0; + this.G = IV[6] | 0; + this.H = IV[7] | 0; + } + get() { + const { A, B, C, D, E, F, G, H } = this; + return [A, B, C, D, E, F, G, H]; + } + set(A, B, C, D, E, F, G, H) { + this.A = A | 0; + this.B = B | 0; + this.C = C | 0; + this.D = D | 0; + this.E = E | 0; + this.F = F | 0; + this.G = G | 0; + this.H = H | 0; + } + process(view, offset) { + for (let i2 = 0; i2 < 16; i2++, offset += 4) + SHA256_W[i2] = view.getUint32(offset, false); + for (let i2 = 16; i2 < 64; i2++) { + const W15 = SHA256_W[i2 - 15]; + const W2 = SHA256_W[i2 - 2]; + const s0 = rotr(W15, 7) ^ rotr(W15, 18) ^ W15 >>> 3; + const s1 = rotr(W2, 17) ^ rotr(W2, 19) ^ W2 >>> 10; + SHA256_W[i2] = s1 + SHA256_W[i2 - 7] + s0 + SHA256_W[i2 - 16] | 0; + } + let { A, B, C, D, E, F, G, H } = this; + for (let i2 = 0; i2 < 64; i2++) { + const sigma1 = rotr(E, 6) ^ rotr(E, 11) ^ rotr(E, 25); + const T1 = H + sigma1 + Chi(E, F, G) + SHA256_K[i2] + SHA256_W[i2] | 0; + const sigma0 = rotr(A, 2) ^ rotr(A, 13) ^ rotr(A, 22); + const T2 = sigma0 + Maj(A, B, C) | 0; + H = G; + G = F; + F = E; + E = D + T1 | 0; + D = C; + C = B; + B = A; + A = T1 + T2 | 0; + } + A = A + this.A | 0; + B = B + this.B | 0; + C = C + this.C | 0; + D = D + this.D | 0; + E = E + this.E | 0; + F = F + this.F | 0; + G = G + this.G | 0; + H = H + this.H | 0; + this.set(A, B, C, D, E, F, G, H); + } + roundClean() { + SHA256_W.fill(0); + } + destroy() { + this.set(0, 0, 0, 0, 0, 0, 0, 0); + this.buffer.fill(0); + } + }; + var sha256 = /* @__PURE__ */ wrapConstructor(() => new SHA256()); + + // node_modules/@noble/curves/esm/abstract/utils.js + var utils_exports = {}; + __export(utils_exports, { + bitGet: () => bitGet, + bitLen: () => bitLen, + bitMask: () => bitMask, + bitSet: () => bitSet, + bytesToHex: () => bytesToHex, + bytesToNumberBE: () => bytesToNumberBE, + bytesToNumberLE: () => bytesToNumberLE, + concatBytes: () => concatBytes2, + createHmacDrbg: () => createHmacDrbg, + ensureBytes: () => ensureBytes, + equalBytes: () => equalBytes, + hexToBytes: () => hexToBytes, + hexToNumber: () => hexToNumber, + numberToBytesBE: () => numberToBytesBE, + numberToBytesLE: () => numberToBytesLE, + numberToHexUnpadded: () => numberToHexUnpadded, + numberToVarBytesBE: () => numberToVarBytesBE, + utf8ToBytes: () => utf8ToBytes2, + validateObject: () => validateObject + }); + var _0n = BigInt(0); + var _1n = BigInt(1); + var _2n = BigInt(2); + var u8a2 = (a) => a instanceof Uint8Array; + var hexes = /* @__PURE__ */ Array.from({ length: 256 }, (_, i2) => i2.toString(16).padStart(2, "0")); + function bytesToHex(bytes4) { + if (!u8a2(bytes4)) + throw new Error("Uint8Array expected"); + let hex2 = ""; + for (let i2 = 0; i2 < bytes4.length; i2++) { + hex2 += hexes[bytes4[i2]]; + } + return hex2; + } + function numberToHexUnpadded(num) { + const hex2 = num.toString(16); + return hex2.length & 1 ? `0${hex2}` : hex2; + } + function hexToNumber(hex2) { + if (typeof hex2 !== "string") + throw new Error("hex string expected, got " + typeof hex2); + return BigInt(hex2 === "" ? "0" : `0x${hex2}`); + } + function hexToBytes(hex2) { + if (typeof hex2 !== "string") + throw new Error("hex string expected, got " + typeof hex2); + const len = hex2.length; + if (len % 2) + throw new Error("padded hex string expected, got unpadded hex of length " + len); + const array = new Uint8Array(len / 2); + for (let i2 = 0; i2 < array.length; i2++) { + const j = i2 * 2; + const hexByte = hex2.slice(j, j + 2); + const byte = Number.parseInt(hexByte, 16); + if (Number.isNaN(byte) || byte < 0) + throw new Error("Invalid byte sequence"); + array[i2] = byte; + } + return array; + } + function bytesToNumberBE(bytes4) { + return hexToNumber(bytesToHex(bytes4)); + } + function bytesToNumberLE(bytes4) { + if (!u8a2(bytes4)) + throw new Error("Uint8Array expected"); + return hexToNumber(bytesToHex(Uint8Array.from(bytes4).reverse())); + } + function numberToBytesBE(n, len) { + return hexToBytes(n.toString(16).padStart(len * 2, "0")); + } + function numberToBytesLE(n, len) { + return numberToBytesBE(n, len).reverse(); + } + function numberToVarBytesBE(n) { + return hexToBytes(numberToHexUnpadded(n)); + } + function ensureBytes(title, hex2, expectedLength) { + let res; + if (typeof hex2 === "string") { + try { + res = hexToBytes(hex2); + } catch (e) { + throw new Error(`${title} must be valid hex string, got "${hex2}". Cause: ${e}`); + } + } else if (u8a2(hex2)) { + res = Uint8Array.from(hex2); + } else { + throw new Error(`${title} must be hex string or Uint8Array`); + } + const len = res.length; + if (typeof expectedLength === "number" && len !== expectedLength) + throw new Error(`${title} expected ${expectedLength} bytes, got ${len}`); + return res; + } + function concatBytes2(...arrays) { + const r = new Uint8Array(arrays.reduce((sum, a) => sum + a.length, 0)); + let pad2 = 0; + arrays.forEach((a) => { + if (!u8a2(a)) + throw new Error("Uint8Array expected"); + r.set(a, pad2); + pad2 += a.length; + }); + return r; + } + function equalBytes(b1, b2) { + if (b1.length !== b2.length) + return false; + for (let i2 = 0; i2 < b1.length; i2++) + if (b1[i2] !== b2[i2]) + return false; + return true; + } + function utf8ToBytes2(str) { + if (typeof str !== "string") + throw new Error(`utf8ToBytes expected string, got ${typeof str}`); + return new Uint8Array(new TextEncoder().encode(str)); + } + function bitLen(n) { + let len; + for (len = 0; n > _0n; n >>= _1n, len += 1) + ; + return len; + } + function bitGet(n, pos) { + return n >> BigInt(pos) & _1n; + } + var bitSet = (n, pos, value) => { + return n | (value ? _1n : _0n) << BigInt(pos); + }; + var bitMask = (n) => (_2n << BigInt(n - 1)) - _1n; + var u8n = (data) => new Uint8Array(data); + var u8fr = (arr) => Uint8Array.from(arr); + function createHmacDrbg(hashLen, qByteLen, hmacFn) { + if (typeof hashLen !== "number" || hashLen < 2) + throw new Error("hashLen must be a number"); + if (typeof qByteLen !== "number" || qByteLen < 2) + throw new Error("qByteLen must be a number"); + if (typeof hmacFn !== "function") + throw new Error("hmacFn must be a function"); + let v = u8n(hashLen); + let k = u8n(hashLen); + let i2 = 0; + const reset = () => { + v.fill(1); + k.fill(0); + i2 = 0; + }; + const h = (...b) => hmacFn(k, v, ...b); + const reseed = (seed = u8n()) => { + k = h(u8fr([0]), seed); + v = h(); + if (seed.length === 0) + return; + k = h(u8fr([1]), seed); + v = h(); + }; + const gen = () => { + if (i2++ >= 1e3) + throw new Error("drbg: tried 1000 values"); + let len = 0; + const out = []; + while (len < qByteLen) { + v = h(); + const sl = v.slice(); + out.push(sl); + len += v.length; + } + return concatBytes2(...out); + }; + const genUntil = (seed, pred) => { + reset(); + reseed(seed); + let res = void 0; + while (!(res = pred(gen()))) + reseed(); + reset(); + return res; + }; + return genUntil; + } + var validatorFns = { + bigint: (val) => typeof val === "bigint", + function: (val) => typeof val === "function", + boolean: (val) => typeof val === "boolean", + string: (val) => typeof val === "string", + stringOrUint8Array: (val) => typeof val === "string" || val instanceof Uint8Array, + isSafeInteger: (val) => Number.isSafeInteger(val), + array: (val) => Array.isArray(val), + field: (val, object) => object.Fp.isValid(val), + hash: (val) => typeof val === "function" && Number.isSafeInteger(val.outputLen) + }; + function validateObject(object, validators, optValidators = {}) { + const checkField = (fieldName, type, isOptional) => { + const checkVal = validatorFns[type]; + if (typeof checkVal !== "function") + throw new Error(`Invalid validator "${type}", expected function`); + const val = object[fieldName]; + if (isOptional && val === void 0) + return; + if (!checkVal(val, object)) { + throw new Error(`Invalid param ${String(fieldName)}=${val} (${typeof val}), expected ${type}`); + } + }; + for (const [fieldName, type] of Object.entries(validators)) + checkField(fieldName, type, false); + for (const [fieldName, type] of Object.entries(optValidators)) + checkField(fieldName, type, true); + return object; + } + + // node_modules/@noble/curves/esm/abstract/modular.js + var _0n2 = BigInt(0); + var _1n2 = BigInt(1); + var _2n2 = BigInt(2); + var _3n = BigInt(3); + var _4n = BigInt(4); + var _5n = BigInt(5); + var _8n = BigInt(8); + var _9n = BigInt(9); + var _16n = BigInt(16); + function mod(a, b) { + const result = a % b; + return result >= _0n2 ? result : b + result; + } + function pow(num, power, modulo) { + if (modulo <= _0n2 || power < _0n2) + throw new Error("Expected power/modulo > 0"); + if (modulo === _1n2) + return _0n2; + let res = _1n2; + while (power > _0n2) { + if (power & _1n2) + res = res * num % modulo; + num = num * num % modulo; + power >>= _1n2; + } + return res; + } + function pow2(x, power, modulo) { + let res = x; + while (power-- > _0n2) { + res *= res; + res %= modulo; + } + return res; + } + function invert(number4, modulo) { + if (number4 === _0n2 || modulo <= _0n2) { + throw new Error(`invert: expected positive integers, got n=${number4} mod=${modulo}`); + } + let a = mod(number4, modulo); + let b = modulo; + let x = _0n2, y = _1n2, u = _1n2, v = _0n2; + while (a !== _0n2) { + const q = b / a; + const r = b % a; + const m = x - u * q; + const n = y - v * q; + b = a, a = r, x = u, y = v, u = m, v = n; + } + const gcd2 = b; + if (gcd2 !== _1n2) + throw new Error("invert: does not exist"); + return mod(x, modulo); + } + function tonelliShanks(P) { + const legendreC = (P - _1n2) / _2n2; + let Q, S, Z; + for (Q = P - _1n2, S = 0; Q % _2n2 === _0n2; Q /= _2n2, S++) + ; + for (Z = _2n2; Z < P && pow(Z, legendreC, P) !== P - _1n2; Z++) + ; + if (S === 1) { + const p1div4 = (P + _1n2) / _4n; + return function tonelliFast(Fp2, n) { + const root = Fp2.pow(n, p1div4); + if (!Fp2.eql(Fp2.sqr(root), n)) + throw new Error("Cannot find square root"); + return root; + }; + } + const Q1div2 = (Q + _1n2) / _2n2; + return function tonelliSlow(Fp2, n) { + if (Fp2.pow(n, legendreC) === Fp2.neg(Fp2.ONE)) + throw new Error("Cannot find square root"); + let r = S; + let g = Fp2.pow(Fp2.mul(Fp2.ONE, Z), Q); + let x = Fp2.pow(n, Q1div2); + let b = Fp2.pow(n, Q); + while (!Fp2.eql(b, Fp2.ONE)) { + if (Fp2.eql(b, Fp2.ZERO)) + return Fp2.ZERO; + let m = 1; + for (let t2 = Fp2.sqr(b); m < r; m++) { + if (Fp2.eql(t2, Fp2.ONE)) + break; + t2 = Fp2.sqr(t2); + } + const ge2 = Fp2.pow(g, _1n2 << BigInt(r - m - 1)); + g = Fp2.sqr(ge2); + x = Fp2.mul(x, ge2); + b = Fp2.mul(b, g); + r = m; + } + return x; + }; + } + function FpSqrt(P) { + if (P % _4n === _3n) { + const p1div4 = (P + _1n2) / _4n; + return function sqrt3mod4(Fp2, n) { + const root = Fp2.pow(n, p1div4); + if (!Fp2.eql(Fp2.sqr(root), n)) + throw new Error("Cannot find square root"); + return root; + }; + } + if (P % _8n === _5n) { + const c1 = (P - _5n) / _8n; + return function sqrt5mod8(Fp2, n) { + const n2 = Fp2.mul(n, _2n2); + const v = Fp2.pow(n2, c1); + const nv = Fp2.mul(n, v); + const i2 = Fp2.mul(Fp2.mul(nv, _2n2), v); + const root = Fp2.mul(nv, Fp2.sub(i2, Fp2.ONE)); + if (!Fp2.eql(Fp2.sqr(root), n)) + throw new Error("Cannot find square root"); + return root; + }; + } + if (P % _16n === _9n) { + } + return tonelliShanks(P); + } + var FIELD_FIELDS = [ + "create", + "isValid", + "is0", + "neg", + "inv", + "sqrt", + "sqr", + "eql", + "add", + "sub", + "mul", + "pow", + "div", + "addN", + "subN", + "mulN", + "sqrN" + ]; + function validateField(field) { + const initial = { + ORDER: "bigint", + MASK: "bigint", + BYTES: "isSafeInteger", + BITS: "isSafeInteger" + }; + const opts = FIELD_FIELDS.reduce((map, val) => { + map[val] = "function"; + return map; + }, initial); + return validateObject(field, opts); + } + function FpPow(f, num, power) { + if (power < _0n2) + throw new Error("Expected power > 0"); + if (power === _0n2) + return f.ONE; + if (power === _1n2) + return num; + let p = f.ONE; + let d = num; + while (power > _0n2) { + if (power & _1n2) + p = f.mul(p, d); + d = f.sqr(d); + power >>= _1n2; + } + return p; + } + function FpInvertBatch(f, nums) { + const tmp = new Array(nums.length); + const lastMultiplied = nums.reduce((acc, num, i2) => { + if (f.is0(num)) + return acc; + tmp[i2] = acc; + return f.mul(acc, num); + }, f.ONE); + const inverted = f.inv(lastMultiplied); + nums.reduceRight((acc, num, i2) => { + if (f.is0(num)) + return acc; + tmp[i2] = f.mul(acc, tmp[i2]); + return f.mul(acc, num); + }, inverted); + return tmp; + } + function nLength(n, nBitLength) { + const _nBitLength = nBitLength !== void 0 ? nBitLength : n.toString(2).length; + const nByteLength = Math.ceil(_nBitLength / 8); + return { nBitLength: _nBitLength, nByteLength }; + } + function Field(ORDER, bitLen2, isLE4 = false, redef = {}) { + if (ORDER <= _0n2) + throw new Error(`Expected Field ORDER > 0, got ${ORDER}`); + const { nBitLength: BITS, nByteLength: BYTES } = nLength(ORDER, bitLen2); + if (BYTES > 2048) + throw new Error("Field lengths over 2048 bytes are not supported"); + const sqrtP = FpSqrt(ORDER); + const f = Object.freeze({ + ORDER, + BITS, + BYTES, + MASK: bitMask(BITS), + ZERO: _0n2, + ONE: _1n2, + create: (num) => mod(num, ORDER), + isValid: (num) => { + if (typeof num !== "bigint") + throw new Error(`Invalid field element: expected bigint, got ${typeof num}`); + return _0n2 <= num && num < ORDER; + }, + is0: (num) => num === _0n2, + isOdd: (num) => (num & _1n2) === _1n2, + neg: (num) => mod(-num, ORDER), + eql: (lhs, rhs) => lhs === rhs, + sqr: (num) => mod(num * num, ORDER), + add: (lhs, rhs) => mod(lhs + rhs, ORDER), + sub: (lhs, rhs) => mod(lhs - rhs, ORDER), + mul: (lhs, rhs) => mod(lhs * rhs, ORDER), + pow: (num, power) => FpPow(f, num, power), + div: (lhs, rhs) => mod(lhs * invert(rhs, ORDER), ORDER), + sqrN: (num) => num * num, + addN: (lhs, rhs) => lhs + rhs, + subN: (lhs, rhs) => lhs - rhs, + mulN: (lhs, rhs) => lhs * rhs, + inv: (num) => invert(num, ORDER), + sqrt: redef.sqrt || ((n) => sqrtP(f, n)), + invertBatch: (lst) => FpInvertBatch(f, lst), + cmov: (a, b, c) => c ? b : a, + toBytes: (num) => isLE4 ? numberToBytesLE(num, BYTES) : numberToBytesBE(num, BYTES), + fromBytes: (bytes4) => { + if (bytes4.length !== BYTES) + throw new Error(`Fp.fromBytes: expected ${BYTES}, got ${bytes4.length}`); + return isLE4 ? bytesToNumberLE(bytes4) : bytesToNumberBE(bytes4); + } + }); + return Object.freeze(f); + } + function getFieldBytesLength(fieldOrder) { + if (typeof fieldOrder !== "bigint") + throw new Error("field order must be bigint"); + const bitLength = fieldOrder.toString(2).length; + return Math.ceil(bitLength / 8); + } + function getMinHashLength(fieldOrder) { + const length = getFieldBytesLength(fieldOrder); + return length + Math.ceil(length / 2); + } + function mapHashToField(key, fieldOrder, isLE4 = false) { + const len = key.length; + const fieldLen = getFieldBytesLength(fieldOrder); + const minLen = getMinHashLength(fieldOrder); + if (len < 16 || len < minLen || len > 1024) + throw new Error(`expected ${minLen}-1024 bytes of input, got ${len}`); + const num = isLE4 ? bytesToNumberBE(key) : bytesToNumberLE(key); + const reduced = mod(num, fieldOrder - _1n2) + _1n2; + return isLE4 ? numberToBytesLE(reduced, fieldLen) : numberToBytesBE(reduced, fieldLen); + } + + // node_modules/@noble/curves/esm/abstract/curve.js + var _0n3 = BigInt(0); + var _1n3 = BigInt(1); + function wNAF(c, bits) { + const constTimeNegate = (condition, item) => { + const neg = item.negate(); + return condition ? neg : item; + }; + const opts = (W) => { + const windows = Math.ceil(bits / W) + 1; + const windowSize = 2 ** (W - 1); + return { windows, windowSize }; + }; + return { + constTimeNegate, + unsafeLadder(elm, n) { + let p = c.ZERO; + let d = elm; + while (n > _0n3) { + if (n & _1n3) + p = p.add(d); + d = d.double(); + n >>= _1n3; + } + return p; + }, + precomputeWindow(elm, W) { + const { windows, windowSize } = opts(W); + const points = []; + let p = elm; + let base = p; + for (let window = 0; window < windows; window++) { + base = p; + points.push(base); + for (let i2 = 1; i2 < windowSize; i2++) { + base = base.add(p); + points.push(base); + } + p = base.double(); + } + return points; + }, + wNAF(W, precomputes, n) { + const { windows, windowSize } = opts(W); + let p = c.ZERO; + let f = c.BASE; + const mask = BigInt(2 ** W - 1); + const maxNumber = 2 ** W; + const shiftBy = BigInt(W); + for (let window = 0; window < windows; window++) { + const offset = window * windowSize; + let wbits = Number(n & mask); + n >>= shiftBy; + if (wbits > windowSize) { + wbits -= maxNumber; + n += _1n3; + } + const offset1 = offset; + const offset2 = offset + Math.abs(wbits) - 1; + const cond1 = window % 2 !== 0; + const cond2 = wbits < 0; + if (wbits === 0) { + f = f.add(constTimeNegate(cond1, precomputes[offset1])); + } else { + p = p.add(constTimeNegate(cond2, precomputes[offset2])); + } + } + return { p, f }; + }, + wNAFCached(P, precomputesMap, n, transform) { + const W = P._WINDOW_SIZE || 1; + let comp = precomputesMap.get(P); + if (!comp) { + comp = this.precomputeWindow(P, W); + if (W !== 1) { + precomputesMap.set(P, transform(comp)); + } + } + return this.wNAF(W, comp, n); + } + }; + } + function validateBasic(curve) { + validateField(curve.Fp); + validateObject(curve, { + n: "bigint", + h: "bigint", + Gx: "field", + Gy: "field" + }, { + nBitLength: "isSafeInteger", + nByteLength: "isSafeInteger" + }); + return Object.freeze({ + ...nLength(curve.n, curve.nBitLength), + ...curve, + ...{ p: curve.Fp.ORDER } + }); + } + + // node_modules/@noble/curves/esm/abstract/weierstrass.js + function validatePointOpts(curve) { + const opts = validateBasic(curve); + validateObject(opts, { + a: "field", + b: "field" + }, { + allowedPrivateKeyLengths: "array", + wrapPrivateKey: "boolean", + isTorsionFree: "function", + clearCofactor: "function", + allowInfinityPoint: "boolean", + fromBytes: "function", + toBytes: "function" + }); + const { endo, Fp: Fp2, a } = opts; + if (endo) { + if (!Fp2.eql(a, Fp2.ZERO)) { + throw new Error("Endomorphism can only be defined for Koblitz curves that have a=0"); + } + if (typeof endo !== "object" || typeof endo.beta !== "bigint" || typeof endo.splitScalar !== "function") { + throw new Error("Expected endomorphism with beta: bigint and splitScalar: function"); + } + } + return Object.freeze({ ...opts }); + } + var { bytesToNumberBE: b2n, hexToBytes: h2b } = utils_exports; + var DER = { + Err: class DERErr extends Error { + constructor(m = "") { + super(m); + } + }, + _parseInt(data) { + const { Err: E } = DER; + if (data.length < 2 || data[0] !== 2) + throw new E("Invalid signature integer tag"); + const len = data[1]; + const res = data.subarray(2, len + 2); + if (!len || res.length !== len) + throw new E("Invalid signature integer: wrong length"); + if (res[0] & 128) + throw new E("Invalid signature integer: negative"); + if (res[0] === 0 && !(res[1] & 128)) + throw new E("Invalid signature integer: unnecessary leading zero"); + return { d: b2n(res), l: data.subarray(len + 2) }; + }, + toSig(hex2) { + const { Err: E } = DER; + const data = typeof hex2 === "string" ? h2b(hex2) : hex2; + if (!(data instanceof Uint8Array)) + throw new Error("ui8a expected"); + let l = data.length; + if (l < 2 || data[0] != 48) + throw new E("Invalid signature tag"); + if (data[1] !== l - 2) + throw new E("Invalid signature: incorrect length"); + const { d: r, l: sBytes } = DER._parseInt(data.subarray(2)); + const { d: s, l: rBytesLeft } = DER._parseInt(sBytes); + if (rBytesLeft.length) + throw new E("Invalid signature: left bytes after parsing"); + return { r, s }; + }, + hexFromSig(sig) { + const slice = (s2) => Number.parseInt(s2[0], 16) & 8 ? "00" + s2 : s2; + const h = (num) => { + const hex2 = num.toString(16); + return hex2.length & 1 ? `0${hex2}` : hex2; + }; + const s = slice(h(sig.s)); + const r = slice(h(sig.r)); + const shl = s.length / 2; + const rhl = r.length / 2; + const sl = h(shl); + const rl = h(rhl); + return `30${h(rhl + shl + 4)}02${rl}${r}02${sl}${s}`; + } + }; + var _0n4 = BigInt(0); + var _1n4 = BigInt(1); + var _2n3 = BigInt(2); + var _3n2 = BigInt(3); + var _4n2 = BigInt(4); + function weierstrassPoints(opts) { + const CURVE = validatePointOpts(opts); + const { Fp: Fp2 } = CURVE; + const toBytes4 = CURVE.toBytes || ((_c, point, _isCompressed) => { + const a = point.toAffine(); + return concatBytes2(Uint8Array.from([4]), Fp2.toBytes(a.x), Fp2.toBytes(a.y)); + }); + const fromBytes = CURVE.fromBytes || ((bytes4) => { + const tail = bytes4.subarray(1); + const x = Fp2.fromBytes(tail.subarray(0, Fp2.BYTES)); + const y = Fp2.fromBytes(tail.subarray(Fp2.BYTES, 2 * Fp2.BYTES)); + return { x, y }; + }); + function weierstrassEquation(x) { + const { a, b } = CURVE; + const x2 = Fp2.sqr(x); + const x3 = Fp2.mul(x2, x); + return Fp2.add(Fp2.add(x3, Fp2.mul(x, a)), b); + } + if (!Fp2.eql(Fp2.sqr(CURVE.Gy), weierstrassEquation(CURVE.Gx))) + throw new Error("bad generator point: equation left != right"); + function isWithinCurveOrder(num) { + return typeof num === "bigint" && _0n4 < num && num < CURVE.n; + } + function assertGE(num) { + if (!isWithinCurveOrder(num)) + throw new Error("Expected valid bigint: 0 < bigint < curve.n"); + } + function normPrivateKeyToScalar(key) { + const { allowedPrivateKeyLengths: lengths, nByteLength, wrapPrivateKey, n } = CURVE; + if (lengths && typeof key !== "bigint") { + if (key instanceof Uint8Array) + key = bytesToHex(key); + if (typeof key !== "string" || !lengths.includes(key.length)) + throw new Error("Invalid key"); + key = key.padStart(nByteLength * 2, "0"); + } + let num; + try { + num = typeof key === "bigint" ? key : bytesToNumberBE(ensureBytes("private key", key, nByteLength)); + } catch (error) { + throw new Error(`private key must be ${nByteLength} bytes, hex or bigint, not ${typeof key}`); + } + if (wrapPrivateKey) + num = mod(num, n); + assertGE(num); + return num; + } + const pointPrecomputes = /* @__PURE__ */ new Map(); + function assertPrjPoint(other) { + if (!(other instanceof Point2)) + throw new Error("ProjectivePoint expected"); + } + class Point2 { + constructor(px, py, pz) { + this.px = px; + this.py = py; + this.pz = pz; + if (px == null || !Fp2.isValid(px)) + throw new Error("x required"); + if (py == null || !Fp2.isValid(py)) + throw new Error("y required"); + if (pz == null || !Fp2.isValid(pz)) + throw new Error("z required"); + } + static fromAffine(p) { + const { x, y } = p || {}; + if (!p || !Fp2.isValid(x) || !Fp2.isValid(y)) + throw new Error("invalid affine point"); + if (p instanceof Point2) + throw new Error("projective point not allowed"); + const is0 = (i2) => Fp2.eql(i2, Fp2.ZERO); + if (is0(x) && is0(y)) + return Point2.ZERO; + return new Point2(x, y, Fp2.ONE); + } + get x() { + return this.toAffine().x; + } + get y() { + return this.toAffine().y; + } + static normalizeZ(points) { + const toInv = Fp2.invertBatch(points.map((p) => p.pz)); + return points.map((p, i2) => p.toAffine(toInv[i2])).map(Point2.fromAffine); + } + static fromHex(hex2) { + const P = Point2.fromAffine(fromBytes(ensureBytes("pointHex", hex2))); + P.assertValidity(); + return P; + } + static fromPrivateKey(privateKey) { + return Point2.BASE.multiply(normPrivateKeyToScalar(privateKey)); + } + _setWindowSize(windowSize) { + this._WINDOW_SIZE = windowSize; + pointPrecomputes.delete(this); + } + assertValidity() { + if (this.is0()) { + if (CURVE.allowInfinityPoint && !Fp2.is0(this.py)) + return; + throw new Error("bad point: ZERO"); + } + const { x, y } = this.toAffine(); + if (!Fp2.isValid(x) || !Fp2.isValid(y)) + throw new Error("bad point: x or y not FE"); + const left = Fp2.sqr(y); + const right = weierstrassEquation(x); + if (!Fp2.eql(left, right)) + throw new Error("bad point: equation left != right"); + if (!this.isTorsionFree()) + throw new Error("bad point: not in prime-order subgroup"); + } + hasEvenY() { + const { y } = this.toAffine(); + if (Fp2.isOdd) + return !Fp2.isOdd(y); + throw new Error("Field doesn't support isOdd"); + } + equals(other) { + assertPrjPoint(other); + const { px: X1, py: Y1, pz: Z1 } = this; + const { px: X2, py: Y2, pz: Z2 } = other; + const U1 = Fp2.eql(Fp2.mul(X1, Z2), Fp2.mul(X2, Z1)); + const U2 = Fp2.eql(Fp2.mul(Y1, Z2), Fp2.mul(Y2, Z1)); + return U1 && U2; + } + negate() { + return new Point2(this.px, Fp2.neg(this.py), this.pz); + } + double() { + const { a, b } = CURVE; + const b3 = Fp2.mul(b, _3n2); + const { px: X1, py: Y1, pz: Z1 } = this; + let X3 = Fp2.ZERO, Y3 = Fp2.ZERO, Z3 = Fp2.ZERO; + let t0 = Fp2.mul(X1, X1); + let t1 = Fp2.mul(Y1, Y1); + let t2 = Fp2.mul(Z1, Z1); + let t3 = Fp2.mul(X1, Y1); + t3 = Fp2.add(t3, t3); + Z3 = Fp2.mul(X1, Z1); + Z3 = Fp2.add(Z3, Z3); + X3 = Fp2.mul(a, Z3); + Y3 = Fp2.mul(b3, t2); + Y3 = Fp2.add(X3, Y3); + X3 = Fp2.sub(t1, Y3); + Y3 = Fp2.add(t1, Y3); + Y3 = Fp2.mul(X3, Y3); + X3 = Fp2.mul(t3, X3); + Z3 = Fp2.mul(b3, Z3); + t2 = Fp2.mul(a, t2); + t3 = Fp2.sub(t0, t2); + t3 = Fp2.mul(a, t3); + t3 = Fp2.add(t3, Z3); + Z3 = Fp2.add(t0, t0); + t0 = Fp2.add(Z3, t0); + t0 = Fp2.add(t0, t2); + t0 = Fp2.mul(t0, t3); + Y3 = Fp2.add(Y3, t0); + t2 = Fp2.mul(Y1, Z1); + t2 = Fp2.add(t2, t2); + t0 = Fp2.mul(t2, t3); + X3 = Fp2.sub(X3, t0); + Z3 = Fp2.mul(t2, t1); + Z3 = Fp2.add(Z3, Z3); + Z3 = Fp2.add(Z3, Z3); + return new Point2(X3, Y3, Z3); + } + add(other) { + assertPrjPoint(other); + const { px: X1, py: Y1, pz: Z1 } = this; + const { px: X2, py: Y2, pz: Z2 } = other; + let X3 = Fp2.ZERO, Y3 = Fp2.ZERO, Z3 = Fp2.ZERO; + const a = CURVE.a; + const b3 = Fp2.mul(CURVE.b, _3n2); + let t0 = Fp2.mul(X1, X2); + let t1 = Fp2.mul(Y1, Y2); + let t2 = Fp2.mul(Z1, Z2); + let t3 = Fp2.add(X1, Y1); + let t4 = Fp2.add(X2, Y2); + t3 = Fp2.mul(t3, t4); + t4 = Fp2.add(t0, t1); + t3 = Fp2.sub(t3, t4); + t4 = Fp2.add(X1, Z1); + let t5 = Fp2.add(X2, Z2); + t4 = Fp2.mul(t4, t5); + t5 = Fp2.add(t0, t2); + t4 = Fp2.sub(t4, t5); + t5 = Fp2.add(Y1, Z1); + X3 = Fp2.add(Y2, Z2); + t5 = Fp2.mul(t5, X3); + X3 = Fp2.add(t1, t2); + t5 = Fp2.sub(t5, X3); + Z3 = Fp2.mul(a, t4); + X3 = Fp2.mul(b3, t2); + Z3 = Fp2.add(X3, Z3); + X3 = Fp2.sub(t1, Z3); + Z3 = Fp2.add(t1, Z3); + Y3 = Fp2.mul(X3, Z3); + t1 = Fp2.add(t0, t0); + t1 = Fp2.add(t1, t0); + t2 = Fp2.mul(a, t2); + t4 = Fp2.mul(b3, t4); + t1 = Fp2.add(t1, t2); + t2 = Fp2.sub(t0, t2); + t2 = Fp2.mul(a, t2); + t4 = Fp2.add(t4, t2); + t0 = Fp2.mul(t1, t4); + Y3 = Fp2.add(Y3, t0); + t0 = Fp2.mul(t5, t4); + X3 = Fp2.mul(t3, X3); + X3 = Fp2.sub(X3, t0); + t0 = Fp2.mul(t3, t1); + Z3 = Fp2.mul(t5, Z3); + Z3 = Fp2.add(Z3, t0); + return new Point2(X3, Y3, Z3); + } + subtract(other) { + return this.add(other.negate()); + } + is0() { + return this.equals(Point2.ZERO); + } + wNAF(n) { + return wnaf.wNAFCached(this, pointPrecomputes, n, (comp) => { + const toInv = Fp2.invertBatch(comp.map((p) => p.pz)); + return comp.map((p, i2) => p.toAffine(toInv[i2])).map(Point2.fromAffine); + }); + } + multiplyUnsafe(n) { + const I = Point2.ZERO; + if (n === _0n4) + return I; + assertGE(n); + if (n === _1n4) + return this; + const { endo } = CURVE; + if (!endo) + return wnaf.unsafeLadder(this, n); + let { k1neg, k1, k2neg, k2 } = endo.splitScalar(n); + let k1p = I; + let k2p = I; + let d = this; + while (k1 > _0n4 || k2 > _0n4) { + if (k1 & _1n4) + k1p = k1p.add(d); + if (k2 & _1n4) + k2p = k2p.add(d); + d = d.double(); + k1 >>= _1n4; + k2 >>= _1n4; + } + if (k1neg) + k1p = k1p.negate(); + if (k2neg) + k2p = k2p.negate(); + k2p = new Point2(Fp2.mul(k2p.px, endo.beta), k2p.py, k2p.pz); + return k1p.add(k2p); + } + multiply(scalar) { + assertGE(scalar); + let n = scalar; + let point, fake; + const { endo } = CURVE; + if (endo) { + const { k1neg, k1, k2neg, k2 } = endo.splitScalar(n); + let { p: k1p, f: f1p } = this.wNAF(k1); + let { p: k2p, f: f2p } = this.wNAF(k2); + k1p = wnaf.constTimeNegate(k1neg, k1p); + k2p = wnaf.constTimeNegate(k2neg, k2p); + k2p = new Point2(Fp2.mul(k2p.px, endo.beta), k2p.py, k2p.pz); + point = k1p.add(k2p); + fake = f1p.add(f2p); + } else { + const { p, f } = this.wNAF(n); + point = p; + fake = f; + } + return Point2.normalizeZ([point, fake])[0]; + } + multiplyAndAddUnsafe(Q, a, b) { + const G = Point2.BASE; + const mul3 = (P, a2) => a2 === _0n4 || a2 === _1n4 || !P.equals(G) ? P.multiplyUnsafe(a2) : P.multiply(a2); + const sum = mul3(this, a).add(mul3(Q, b)); + return sum.is0() ? void 0 : sum; + } + toAffine(iz) { + const { px: x, py: y, pz: z } = this; + const is0 = this.is0(); + if (iz == null) + iz = is0 ? Fp2.ONE : Fp2.inv(z); + const ax = Fp2.mul(x, iz); + const ay = Fp2.mul(y, iz); + const zz = Fp2.mul(z, iz); + if (is0) + return { x: Fp2.ZERO, y: Fp2.ZERO }; + if (!Fp2.eql(zz, Fp2.ONE)) + throw new Error("invZ was invalid"); + return { x: ax, y: ay }; + } + isTorsionFree() { + const { h: cofactor, isTorsionFree } = CURVE; + if (cofactor === _1n4) + return true; + if (isTorsionFree) + return isTorsionFree(Point2, this); + throw new Error("isTorsionFree() has not been declared for the elliptic curve"); + } + clearCofactor() { + const { h: cofactor, clearCofactor } = CURVE; + if (cofactor === _1n4) + return this; + if (clearCofactor) + return clearCofactor(Point2, this); + return this.multiplyUnsafe(CURVE.h); + } + toRawBytes(isCompressed = true) { + this.assertValidity(); + return toBytes4(Point2, this, isCompressed); + } + toHex(isCompressed = true) { + return bytesToHex(this.toRawBytes(isCompressed)); + } + } + Point2.BASE = new Point2(CURVE.Gx, CURVE.Gy, Fp2.ONE); + Point2.ZERO = new Point2(Fp2.ZERO, Fp2.ONE, Fp2.ZERO); + const _bits = CURVE.nBitLength; + const wnaf = wNAF(Point2, CURVE.endo ? Math.ceil(_bits / 2) : _bits); + return { + CURVE, + ProjectivePoint: Point2, + normPrivateKeyToScalar, + weierstrassEquation, + isWithinCurveOrder + }; + } + function validateOpts(curve) { + const opts = validateBasic(curve); + validateObject(opts, { + hash: "hash", + hmac: "function", + randomBytes: "function" + }, { + bits2int: "function", + bits2int_modN: "function", + lowS: "boolean" + }); + return Object.freeze({ lowS: true, ...opts }); + } + function weierstrass(curveDef) { + const CURVE = validateOpts(curveDef); + const { Fp: Fp2, n: CURVE_ORDER } = CURVE; + const compressedLen = Fp2.BYTES + 1; + const uncompressedLen = 2 * Fp2.BYTES + 1; + function isValidFieldElement(num) { + return _0n4 < num && num < Fp2.ORDER; + } + function modN2(a) { + return mod(a, CURVE_ORDER); + } + function invN(a) { + return invert(a, CURVE_ORDER); + } + const { ProjectivePoint: Point2, normPrivateKeyToScalar, weierstrassEquation, isWithinCurveOrder } = weierstrassPoints({ + ...CURVE, + toBytes(_c, point, isCompressed) { + const a = point.toAffine(); + const x = Fp2.toBytes(a.x); + const cat = concatBytes2; + if (isCompressed) { + return cat(Uint8Array.from([point.hasEvenY() ? 2 : 3]), x); + } else { + return cat(Uint8Array.from([4]), x, Fp2.toBytes(a.y)); + } + }, + fromBytes(bytes4) { + const len = bytes4.length; + const head = bytes4[0]; + const tail = bytes4.subarray(1); + if (len === compressedLen && (head === 2 || head === 3)) { + const x = bytesToNumberBE(tail); + if (!isValidFieldElement(x)) + throw new Error("Point is not on curve"); + const y2 = weierstrassEquation(x); + let y = Fp2.sqrt(y2); + const isYOdd = (y & _1n4) === _1n4; + const isHeadOdd = (head & 1) === 1; + if (isHeadOdd !== isYOdd) + y = Fp2.neg(y); + return { x, y }; + } else if (len === uncompressedLen && head === 4) { + const x = Fp2.fromBytes(tail.subarray(0, Fp2.BYTES)); + const y = Fp2.fromBytes(tail.subarray(Fp2.BYTES, 2 * Fp2.BYTES)); + return { x, y }; + } else { + throw new Error(`Point of length ${len} was invalid. Expected ${compressedLen} compressed bytes or ${uncompressedLen} uncompressed bytes`); + } + } + }); + const numToNByteStr = (num) => bytesToHex(numberToBytesBE(num, CURVE.nByteLength)); + function isBiggerThanHalfOrder(number4) { + const HALF = CURVE_ORDER >> _1n4; + return number4 > HALF; + } + function normalizeS(s) { + return isBiggerThanHalfOrder(s) ? modN2(-s) : s; + } + const slcNum = (b, from, to) => bytesToNumberBE(b.slice(from, to)); + class Signature { + constructor(r, s, recovery) { + this.r = r; + this.s = s; + this.recovery = recovery; + this.assertValidity(); + } + static fromCompact(hex2) { + const l = CURVE.nByteLength; + hex2 = ensureBytes("compactSignature", hex2, l * 2); + return new Signature(slcNum(hex2, 0, l), slcNum(hex2, l, 2 * l)); + } + static fromDER(hex2) { + const { r, s } = DER.toSig(ensureBytes("DER", hex2)); + return new Signature(r, s); + } + assertValidity() { + if (!isWithinCurveOrder(this.r)) + throw new Error("r must be 0 < r < CURVE.n"); + if (!isWithinCurveOrder(this.s)) + throw new Error("s must be 0 < s < CURVE.n"); + } + addRecoveryBit(recovery) { + return new Signature(this.r, this.s, recovery); + } + recoverPublicKey(msgHash) { + const { r, s, recovery: rec } = this; + const h = bits2int_modN(ensureBytes("msgHash", msgHash)); + if (rec == null || ![0, 1, 2, 3].includes(rec)) + throw new Error("recovery id invalid"); + const radj = rec === 2 || rec === 3 ? r + CURVE.n : r; + if (radj >= Fp2.ORDER) + throw new Error("recovery id 2 or 3 invalid"); + const prefix = (rec & 1) === 0 ? "02" : "03"; + const R = Point2.fromHex(prefix + numToNByteStr(radj)); + const ir = invN(radj); + const u1 = modN2(-h * ir); + const u2 = modN2(s * ir); + const Q = Point2.BASE.multiplyAndAddUnsafe(R, u1, u2); + if (!Q) + throw new Error("point at infinify"); + Q.assertValidity(); + return Q; + } + hasHighS() { + return isBiggerThanHalfOrder(this.s); + } + normalizeS() { + return this.hasHighS() ? new Signature(this.r, modN2(-this.s), this.recovery) : this; + } + toDERRawBytes() { + return hexToBytes(this.toDERHex()); + } + toDERHex() { + return DER.hexFromSig({ r: this.r, s: this.s }); + } + toCompactRawBytes() { + return hexToBytes(this.toCompactHex()); + } + toCompactHex() { + return numToNByteStr(this.r) + numToNByteStr(this.s); + } + } + const utils = { + isValidPrivateKey(privateKey) { + try { + normPrivateKeyToScalar(privateKey); + return true; + } catch (error) { + return false; + } + }, + normPrivateKeyToScalar, + randomPrivateKey: () => { + const length = getMinHashLength(CURVE.n); + return mapHashToField(CURVE.randomBytes(length), CURVE.n); + }, + precompute(windowSize = 8, point = Point2.BASE) { + point._setWindowSize(windowSize); + point.multiply(BigInt(3)); + return point; + } + }; + function getPublicKey2(privateKey, isCompressed = true) { + return Point2.fromPrivateKey(privateKey).toRawBytes(isCompressed); + } + function isProbPub(item) { + const arr = item instanceof Uint8Array; + const str = typeof item === "string"; + const len = (arr || str) && item.length; + if (arr) + return len === compressedLen || len === uncompressedLen; + if (str) + return len === 2 * compressedLen || len === 2 * uncompressedLen; + if (item instanceof Point2) + return true; + return false; + } + function getSharedSecret(privateA, publicB, isCompressed = true) { + if (isProbPub(privateA)) + throw new Error("first arg must be private key"); + if (!isProbPub(publicB)) + throw new Error("second arg must be public key"); + const b = Point2.fromHex(publicB); + return b.multiply(normPrivateKeyToScalar(privateA)).toRawBytes(isCompressed); + } + const bits2int = CURVE.bits2int || function(bytes4) { + const num = bytesToNumberBE(bytes4); + const delta = bytes4.length * 8 - CURVE.nBitLength; + return delta > 0 ? num >> BigInt(delta) : num; + }; + const bits2int_modN = CURVE.bits2int_modN || function(bytes4) { + return modN2(bits2int(bytes4)); + }; + const ORDER_MASK = bitMask(CURVE.nBitLength); + function int2octets(num) { + if (typeof num !== "bigint") + throw new Error("bigint expected"); + if (!(_0n4 <= num && num < ORDER_MASK)) + throw new Error(`bigint expected < 2^${CURVE.nBitLength}`); + return numberToBytesBE(num, CURVE.nByteLength); + } + function prepSig(msgHash, privateKey, opts = defaultSigOpts) { + if (["recovered", "canonical"].some((k) => k in opts)) + throw new Error("sign() legacy options not supported"); + const { hash: hash3, randomBytes: randomBytes3 } = CURVE; + let { lowS, prehash, extraEntropy: ent } = opts; + if (lowS == null) + lowS = true; + msgHash = ensureBytes("msgHash", msgHash); + if (prehash) + msgHash = ensureBytes("prehashed msgHash", hash3(msgHash)); + const h1int = bits2int_modN(msgHash); + const d = normPrivateKeyToScalar(privateKey); + const seedArgs = [int2octets(d), int2octets(h1int)]; + if (ent != null) { + const e = ent === true ? randomBytes3(Fp2.BYTES) : ent; + seedArgs.push(ensureBytes("extraEntropy", e)); + } + const seed = concatBytes2(...seedArgs); + const m = h1int; + function k2sig(kBytes) { + const k = bits2int(kBytes); + if (!isWithinCurveOrder(k)) + return; + const ik = invN(k); + const q = Point2.BASE.multiply(k).toAffine(); + const r = modN2(q.x); + if (r === _0n4) + return; + const s = modN2(ik * modN2(m + r * d)); + if (s === _0n4) + return; + let recovery = (q.x === r ? 0 : 2) | Number(q.y & _1n4); + let normS = s; + if (lowS && isBiggerThanHalfOrder(s)) { + normS = normalizeS(s); + recovery ^= 1; + } + return new Signature(r, normS, recovery); + } + return { seed, k2sig }; + } + const defaultSigOpts = { lowS: CURVE.lowS, prehash: false }; + const defaultVerOpts = { lowS: CURVE.lowS, prehash: false }; + function sign(msgHash, privKey, opts = defaultSigOpts) { + const { seed, k2sig } = prepSig(msgHash, privKey, opts); + const C = CURVE; + const drbg = createHmacDrbg(C.hash.outputLen, C.nByteLength, C.hmac); + return drbg(seed, k2sig); + } + Point2.BASE._setWindowSize(8); + function verify(signature, msgHash, publicKey, opts = defaultVerOpts) { + const sg = signature; + msgHash = ensureBytes("msgHash", msgHash); + publicKey = ensureBytes("publicKey", publicKey); + if ("strict" in opts) + throw new Error("options.strict was renamed to lowS"); + const { lowS, prehash } = opts; + let _sig = void 0; + let P; + try { + if (typeof sg === "string" || sg instanceof Uint8Array) { + try { + _sig = Signature.fromDER(sg); + } catch (derError) { + if (!(derError instanceof DER.Err)) + throw derError; + _sig = Signature.fromCompact(sg); + } + } else if (typeof sg === "object" && typeof sg.r === "bigint" && typeof sg.s === "bigint") { + const { r: r2, s: s2 } = sg; + _sig = new Signature(r2, s2); + } else { + throw new Error("PARSE"); + } + P = Point2.fromHex(publicKey); + } catch (error) { + if (error.message === "PARSE") + throw new Error(`signature must be Signature instance, Uint8Array or hex string`); + return false; + } + if (lowS && _sig.hasHighS()) + return false; + if (prehash) + msgHash = CURVE.hash(msgHash); + const { r, s } = _sig; + const h = bits2int_modN(msgHash); + const is = invN(s); + const u1 = modN2(h * is); + const u2 = modN2(r * is); + const R = Point2.BASE.multiplyAndAddUnsafe(P, u1, u2)?.toAffine(); + if (!R) + return false; + const v = modN2(R.x); + return v === r; + } + return { + CURVE, + getPublicKey: getPublicKey2, + getSharedSecret, + sign, + verify, + ProjectivePoint: Point2, + Signature, + utils + }; + } + + // node_modules/@noble/curves/node_modules/@noble/hashes/esm/hmac.js + var HMAC = class extends Hash { + constructor(hash3, _key) { + super(); + this.finished = false; + this.destroyed = false; + hash(hash3); + const key = toBytes(_key); + this.iHash = hash3.create(); + if (typeof this.iHash.update !== "function") + throw new Error("Expected instance of class which extends utils.Hash"); + this.blockLen = this.iHash.blockLen; + this.outputLen = this.iHash.outputLen; + const blockLen = this.blockLen; + const pad2 = new Uint8Array(blockLen); + pad2.set(key.length > blockLen ? hash3.create().update(key).digest() : key); + for (let i2 = 0; i2 < pad2.length; i2++) + pad2[i2] ^= 54; + this.iHash.update(pad2); + this.oHash = hash3.create(); + for (let i2 = 0; i2 < pad2.length; i2++) + pad2[i2] ^= 54 ^ 92; + this.oHash.update(pad2); + pad2.fill(0); + } + update(buf) { + exists(this); + this.iHash.update(buf); + return this; + } + digestInto(out) { + exists(this); + bytes(out, this.outputLen); + this.finished = true; + this.iHash.digestInto(out); + this.oHash.update(out); + this.oHash.digestInto(out); + this.destroy(); + } + digest() { + const out = new Uint8Array(this.oHash.outputLen); + this.digestInto(out); + return out; + } + _cloneInto(to) { + to || (to = Object.create(Object.getPrototypeOf(this), {})); + const { oHash, iHash, finished, destroyed, blockLen, outputLen } = this; + to = to; + to.finished = finished; + to.destroyed = destroyed; + to.blockLen = blockLen; + to.outputLen = outputLen; + to.oHash = oHash._cloneInto(to.oHash); + to.iHash = iHash._cloneInto(to.iHash); + return to; + } + destroy() { + this.destroyed = true; + this.oHash.destroy(); + this.iHash.destroy(); + } + }; + var hmac = (hash3, key, message) => new HMAC(hash3, key).update(message).digest(); + hmac.create = (hash3, key) => new HMAC(hash3, key); + + // node_modules/@noble/curves/esm/_shortw_utils.js + function getHash(hash3) { + return { + hash: hash3, + hmac: (key, ...msgs) => hmac(hash3, key, concatBytes(...msgs)), + randomBytes + }; + } + function createCurve(curveDef, defHash) { + const create = (hash3) => weierstrass({ ...curveDef, ...getHash(hash3) }); + return Object.freeze({ ...create(defHash), create }); + } + + // node_modules/@noble/curves/esm/secp256k1.js + var secp256k1P = BigInt("0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f"); + var secp256k1N = BigInt("0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141"); + var _1n5 = BigInt(1); + var _2n4 = BigInt(2); + var divNearest = (a, b) => (a + b / _2n4) / b; + function sqrtMod(y) { + const P = secp256k1P; + const _3n3 = BigInt(3), _6n = BigInt(6), _11n = BigInt(11), _22n = BigInt(22); + const _23n = BigInt(23), _44n = BigInt(44), _88n = BigInt(88); + const b2 = y * y * y % P; + const b3 = b2 * b2 * y % P; + const b6 = pow2(b3, _3n3, P) * b3 % P; + const b9 = pow2(b6, _3n3, P) * b3 % P; + const b11 = pow2(b9, _2n4, P) * b2 % P; + const b22 = pow2(b11, _11n, P) * b11 % P; + const b44 = pow2(b22, _22n, P) * b22 % P; + const b88 = pow2(b44, _44n, P) * b44 % P; + const b176 = pow2(b88, _88n, P) * b88 % P; + const b220 = pow2(b176, _44n, P) * b44 % P; + const b223 = pow2(b220, _3n3, P) * b3 % P; + const t1 = pow2(b223, _23n, P) * b22 % P; + const t2 = pow2(t1, _6n, P) * b2 % P; + const root = pow2(t2, _2n4, P); + if (!Fp.eql(Fp.sqr(root), y)) + throw new Error("Cannot find square root"); + return root; + } + var Fp = Field(secp256k1P, void 0, void 0, { sqrt: sqrtMod }); + var secp256k1 = createCurve({ + a: BigInt(0), + b: BigInt(7), + Fp, + n: secp256k1N, + Gx: BigInt("55066263022277343669578718895168534326250603453777594175500187360389116729240"), + Gy: BigInt("32670510020758816978083085130507043184471273380659243275938904335757337482424"), + h: BigInt(1), + lowS: true, + endo: { + beta: BigInt("0x7ae96a2b657c07106e64479eac3434e99cf0497512f58995c1396c28719501ee"), + splitScalar: (k) => { + const n = secp256k1N; + const a1 = BigInt("0x3086d221a7d46bcde86c90e49284eb15"); + const b1 = -_1n5 * BigInt("0xe4437ed6010e88286f547fa90abfe4c3"); + const a2 = BigInt("0x114ca50f7a8e2f3f657c1108d9d44cfd8"); + const b2 = a1; + const POW_2_128 = BigInt("0x100000000000000000000000000000000"); + const c1 = divNearest(b2 * k, n); + const c2 = divNearest(-b1 * k, n); + let k1 = mod(k - c1 * a1 - c2 * a2, n); + let k2 = mod(-c1 * b1 - c2 * b2, n); + const k1neg = k1 > POW_2_128; + const k2neg = k2 > POW_2_128; + if (k1neg) + k1 = n - k1; + if (k2neg) + k2 = n - k2; + if (k1 > POW_2_128 || k2 > POW_2_128) { + throw new Error("splitScalar: Endomorphism failed, k=" + k); + } + return { k1neg, k1, k2neg, k2 }; + } + } + }, sha256); + var _0n5 = BigInt(0); + var fe = (x) => typeof x === "bigint" && _0n5 < x && x < secp256k1P; + var ge = (x) => typeof x === "bigint" && _0n5 < x && x < secp256k1N; + var TAGGED_HASH_PREFIXES = {}; + function taggedHash(tag, ...messages) { + let tagP = TAGGED_HASH_PREFIXES[tag]; + if (tagP === void 0) { + const tagH = sha256(Uint8Array.from(tag, (c) => c.charCodeAt(0))); + tagP = concatBytes2(tagH, tagH); + TAGGED_HASH_PREFIXES[tag] = tagP; + } + return sha256(concatBytes2(tagP, ...messages)); + } + var pointToBytes = (point) => point.toRawBytes(true).slice(1); + var numTo32b = (n) => numberToBytesBE(n, 32); + var modP = (x) => mod(x, secp256k1P); + var modN = (x) => mod(x, secp256k1N); + var Point = secp256k1.ProjectivePoint; + var GmulAdd = (Q, a, b) => Point.BASE.multiplyAndAddUnsafe(Q, a, b); + function schnorrGetExtPubKey(priv) { + let d_ = secp256k1.utils.normPrivateKeyToScalar(priv); + let p = Point.fromPrivateKey(d_); + const scalar = p.hasEvenY() ? d_ : modN(-d_); + return { scalar, bytes: pointToBytes(p) }; + } + function lift_x(x) { + if (!fe(x)) + throw new Error("bad x: need 0 < x < p"); + const xx = modP(x * x); + const c = modP(xx * x + BigInt(7)); + let y = sqrtMod(c); + if (y % _2n4 !== _0n5) + y = modP(-y); + const p = new Point(x, y, _1n5); + p.assertValidity(); + return p; + } + function challenge(...args) { + return modN(bytesToNumberBE(taggedHash("BIP0340/challenge", ...args))); + } + function schnorrGetPublicKey(privateKey) { + return schnorrGetExtPubKey(privateKey).bytes; + } + function schnorrSign(message, privateKey, auxRand = randomBytes(32)) { + const m = ensureBytes("message", message); + const { bytes: px, scalar: d } = schnorrGetExtPubKey(privateKey); + const a = ensureBytes("auxRand", auxRand, 32); + const t = numTo32b(d ^ bytesToNumberBE(taggedHash("BIP0340/aux", a))); + const rand = taggedHash("BIP0340/nonce", t, px, m); + const k_ = modN(bytesToNumberBE(rand)); + if (k_ === _0n5) + throw new Error("sign failed: k is zero"); + const { bytes: rx, scalar: k } = schnorrGetExtPubKey(k_); + const e = challenge(rx, px, m); + const sig = new Uint8Array(64); + sig.set(rx, 0); + sig.set(numTo32b(modN(k + e * d)), 32); + if (!schnorrVerify(sig, m, px)) + throw new Error("sign: Invalid signature produced"); + return sig; + } + function schnorrVerify(signature, message, publicKey) { + const sig = ensureBytes("signature", signature, 64); + const m = ensureBytes("message", message); + const pub = ensureBytes("publicKey", publicKey, 32); + try { + const P = lift_x(bytesToNumberBE(pub)); + const r = bytesToNumberBE(sig.subarray(0, 32)); + if (!fe(r)) + return false; + const s = bytesToNumberBE(sig.subarray(32, 64)); + if (!ge(s)) + return false; + const e = challenge(numTo32b(r), pointToBytes(P), m); + const R = GmulAdd(P, s, modN(-e)); + if (!R || !R.hasEvenY() || R.toAffine().x !== r) + return false; + return true; + } catch (error) { + return false; + } + } + var schnorr = /* @__PURE__ */ (() => ({ + getPublicKey: schnorrGetPublicKey, + sign: schnorrSign, + verify: schnorrVerify, + utils: { + randomPrivateKey: secp256k1.utils.randomPrivateKey, + lift_x, + pointToBytes, + numberToBytesBE, + bytesToNumberBE, + taggedHash, + mod + } + }))(); + + // node_modules/@noble/hashes/esm/crypto.js + var crypto2 = typeof globalThis === "object" && "crypto" in globalThis ? globalThis.crypto : void 0; + + // node_modules/@noble/hashes/esm/utils.js + var u8a3 = (a) => a instanceof Uint8Array; + var createView2 = (arr) => new DataView(arr.buffer, arr.byteOffset, arr.byteLength); + var rotr2 = (word, shift) => word << 32 - shift | word >>> shift; + var isLE2 = new Uint8Array(new Uint32Array([287454020]).buffer)[0] === 68; + if (!isLE2) + throw new Error("Non little-endian hardware is not supported"); + var hexes2 = Array.from({ length: 256 }, (v, i2) => i2.toString(16).padStart(2, "0")); + function bytesToHex2(bytes4) { + if (!u8a3(bytes4)) + throw new Error("Uint8Array expected"); + let hex2 = ""; + for (let i2 = 0; i2 < bytes4.length; i2++) { + hex2 += hexes2[bytes4[i2]]; + } + return hex2; + } + function hexToBytes2(hex2) { + if (typeof hex2 !== "string") + throw new Error("hex string expected, got " + typeof hex2); + const len = hex2.length; + if (len % 2) + throw new Error("padded hex string expected, got unpadded hex of length " + len); + const array = new Uint8Array(len / 2); + for (let i2 = 0; i2 < array.length; i2++) { + const j = i2 * 2; + const hexByte = hex2.slice(j, j + 2); + const byte = Number.parseInt(hexByte, 16); + if (Number.isNaN(byte) || byte < 0) + throw new Error("Invalid byte sequence"); + array[i2] = byte; + } + return array; + } + function utf8ToBytes3(str) { + if (typeof str !== "string") + throw new Error(`utf8ToBytes expected string, got ${typeof str}`); + return new Uint8Array(new TextEncoder().encode(str)); + } + function toBytes2(data) { + if (typeof data === "string") + data = utf8ToBytes3(data); + if (!u8a3(data)) + throw new Error(`expected Uint8Array, got ${typeof data}`); + return data; + } + function concatBytes3(...arrays) { + const r = new Uint8Array(arrays.reduce((sum, a) => sum + a.length, 0)); + let pad2 = 0; + arrays.forEach((a) => { + if (!u8a3(a)) + throw new Error("Uint8Array expected"); + r.set(a, pad2); + pad2 += a.length; + }); + return r; + } + var Hash2 = class { + clone() { + return this._cloneInto(); + } + }; + function wrapConstructor2(hashCons) { + const hashC = (msg) => hashCons().update(toBytes2(msg)).digest(); + const tmp = hashCons(); + hashC.outputLen = tmp.outputLen; + hashC.blockLen = tmp.blockLen; + hashC.create = () => hashCons(); + return hashC; + } + function randomBytes2(bytesLength = 32) { + if (crypto2 && typeof crypto2.getRandomValues === "function") { + return crypto2.getRandomValues(new Uint8Array(bytesLength)); + } + throw new Error("crypto.getRandomValues must be defined"); + } + + // core.ts + var verifiedSymbol = Symbol("verified"); + var isRecord = (obj) => obj instanceof Object; + function validateEvent(event) { + if (!isRecord(event)) + return false; + if (typeof event.kind !== "number") + return false; + if (typeof event.content !== "string") + return false; + if (typeof event.created_at !== "number") + return false; + if (typeof event.pubkey !== "string") + return false; + if (!event.pubkey.match(/^[a-f0-9]{64}$/)) + return false; + if (!Array.isArray(event.tags)) + return false; + for (let i2 = 0; i2 < event.tags.length; i2++) { + let tag = event.tags[i2]; + if (!Array.isArray(tag)) + return false; + for (let j = 0; j < tag.length; j++) { + if (typeof tag[j] !== "string") + return false; + } + } + return true; + } + function sortEvents(events) { + return events.sort((a, b) => { + if (a.created_at !== b.created_at) { + return b.created_at - a.created_at; + } + return a.id.localeCompare(b.id); + }); + } + + // node_modules/@noble/hashes/esm/_assert.js + function number2(n) { + if (!Number.isSafeInteger(n) || n < 0) + throw new Error(`Wrong positive integer: ${n}`); + } + function bool(b) { + if (typeof b !== "boolean") + throw new Error(`Expected boolean, not ${b}`); + } + function bytes2(b, ...lengths) { + if (!(b instanceof Uint8Array)) + throw new Error("Expected Uint8Array"); + if (lengths.length > 0 && !lengths.includes(b.length)) + throw new Error(`Expected Uint8Array of length ${lengths}, not of length=${b.length}`); + } + function hash2(hash3) { + if (typeof hash3 !== "function" || typeof hash3.create !== "function") + throw new Error("Hash should be wrapped by utils.wrapConstructor"); + number2(hash3.outputLen); + number2(hash3.blockLen); + } + function exists2(instance, checkFinished = true) { + if (instance.destroyed) + throw new Error("Hash instance has been destroyed"); + if (checkFinished && instance.finished) + throw new Error("Hash#digest() has already been called"); + } + function output2(out, instance) { + bytes2(out); + const min = instance.outputLen; + if (out.length < min) { + throw new Error(`digestInto() expects output buffer of length at least ${min}`); + } + } + var assert = { + number: number2, + bool, + bytes: bytes2, + hash: hash2, + exists: exists2, + output: output2 + }; + var assert_default = assert; + + // node_modules/@noble/hashes/esm/_sha2.js + function setBigUint642(view, byteOffset, value, isLE4) { + if (typeof view.setBigUint64 === "function") + return view.setBigUint64(byteOffset, value, isLE4); + const _32n = BigInt(32); + const _u32_max = BigInt(4294967295); + const wh = Number(value >> _32n & _u32_max); + const wl = Number(value & _u32_max); + const h = isLE4 ? 4 : 0; + const l = isLE4 ? 0 : 4; + view.setUint32(byteOffset + h, wh, isLE4); + view.setUint32(byteOffset + l, wl, isLE4); + } + var SHA22 = class extends Hash2 { + constructor(blockLen, outputLen, padOffset, isLE4) { + super(); + this.blockLen = blockLen; + this.outputLen = outputLen; + this.padOffset = padOffset; + this.isLE = isLE4; + this.finished = false; + this.length = 0; + this.pos = 0; + this.destroyed = false; + this.buffer = new Uint8Array(blockLen); + this.view = createView2(this.buffer); + } + update(data) { + assert_default.exists(this); + const { view, buffer, blockLen } = this; + data = toBytes2(data); + const len = data.length; + for (let pos = 0; pos < len; ) { + const take = Math.min(blockLen - this.pos, len - pos); + if (take === blockLen) { + const dataView = createView2(data); + for (; blockLen <= len - pos; pos += blockLen) + this.process(dataView, pos); + continue; + } + buffer.set(data.subarray(pos, pos + take), this.pos); + this.pos += take; + pos += take; + if (this.pos === blockLen) { + this.process(view, 0); + this.pos = 0; + } + } + this.length += data.length; + this.roundClean(); + return this; + } + digestInto(out) { + assert_default.exists(this); + assert_default.output(out, this); + this.finished = true; + const { buffer, view, blockLen, isLE: isLE4 } = this; + let { pos } = this; + buffer[pos++] = 128; + this.buffer.subarray(pos).fill(0); + if (this.padOffset > blockLen - pos) { + this.process(view, 0); + pos = 0; + } + for (let i2 = pos; i2 < blockLen; i2++) + buffer[i2] = 0; + setBigUint642(view, blockLen - 8, BigInt(this.length * 8), isLE4); + this.process(view, 0); + const oview = createView2(out); + const len = this.outputLen; + if (len % 4) + throw new Error("_sha2: outputLen should be aligned to 32bit"); + const outLen = len / 4; + const state = this.get(); + if (outLen > state.length) + throw new Error("_sha2: outputLen bigger than state"); + for (let i2 = 0; i2 < outLen; i2++) + oview.setUint32(4 * i2, state[i2], isLE4); + } + digest() { + const { buffer, outputLen } = this; + this.digestInto(buffer); + const res = buffer.slice(0, outputLen); + this.destroy(); + return res; + } + _cloneInto(to) { + to || (to = new this.constructor()); + to.set(...this.get()); + const { blockLen, buffer, length, finished, destroyed, pos } = this; + to.length = length; + to.pos = pos; + to.finished = finished; + to.destroyed = destroyed; + if (length % blockLen) + to.buffer.set(buffer); + return to; + } + }; + + // node_modules/@noble/hashes/esm/sha256.js + var Chi2 = (a, b, c) => a & b ^ ~a & c; + var Maj2 = (a, b, c) => a & b ^ a & c ^ b & c; + var SHA256_K2 = new Uint32Array([ + 1116352408, + 1899447441, + 3049323471, + 3921009573, + 961987163, + 1508970993, + 2453635748, + 2870763221, + 3624381080, + 310598401, + 607225278, + 1426881987, + 1925078388, + 2162078206, + 2614888103, + 3248222580, + 3835390401, + 4022224774, + 264347078, + 604807628, + 770255983, + 1249150122, + 1555081692, + 1996064986, + 2554220882, + 2821834349, + 2952996808, + 3210313671, + 3336571891, + 3584528711, + 113926993, + 338241895, + 666307205, + 773529912, + 1294757372, + 1396182291, + 1695183700, + 1986661051, + 2177026350, + 2456956037, + 2730485921, + 2820302411, + 3259730800, + 3345764771, + 3516065817, + 3600352804, + 4094571909, + 275423344, + 430227734, + 506948616, + 659060556, + 883997877, + 958139571, + 1322822218, + 1537002063, + 1747873779, + 1955562222, + 2024104815, + 2227730452, + 2361852424, + 2428436474, + 2756734187, + 3204031479, + 3329325298 + ]); + var IV2 = new Uint32Array([ + 1779033703, + 3144134277, + 1013904242, + 2773480762, + 1359893119, + 2600822924, + 528734635, + 1541459225 + ]); + var SHA256_W2 = new Uint32Array(64); + var SHA2562 = class extends SHA22 { + constructor() { + super(64, 32, 8, false); + this.A = IV2[0] | 0; + this.B = IV2[1] | 0; + this.C = IV2[2] | 0; + this.D = IV2[3] | 0; + this.E = IV2[4] | 0; + this.F = IV2[5] | 0; + this.G = IV2[6] | 0; + this.H = IV2[7] | 0; + } + get() { + const { A, B, C, D, E, F, G, H } = this; + return [A, B, C, D, E, F, G, H]; + } + set(A, B, C, D, E, F, G, H) { + this.A = A | 0; + this.B = B | 0; + this.C = C | 0; + this.D = D | 0; + this.E = E | 0; + this.F = F | 0; + this.G = G | 0; + this.H = H | 0; + } + process(view, offset) { + for (let i2 = 0; i2 < 16; i2++, offset += 4) + SHA256_W2[i2] = view.getUint32(offset, false); + for (let i2 = 16; i2 < 64; i2++) { + const W15 = SHA256_W2[i2 - 15]; + const W2 = SHA256_W2[i2 - 2]; + const s0 = rotr2(W15, 7) ^ rotr2(W15, 18) ^ W15 >>> 3; + const s1 = rotr2(W2, 17) ^ rotr2(W2, 19) ^ W2 >>> 10; + SHA256_W2[i2] = s1 + SHA256_W2[i2 - 7] + s0 + SHA256_W2[i2 - 16] | 0; + } + let { A, B, C, D, E, F, G, H } = this; + for (let i2 = 0; i2 < 64; i2++) { + const sigma1 = rotr2(E, 6) ^ rotr2(E, 11) ^ rotr2(E, 25); + const T1 = H + sigma1 + Chi2(E, F, G) + SHA256_K2[i2] + SHA256_W2[i2] | 0; + const sigma0 = rotr2(A, 2) ^ rotr2(A, 13) ^ rotr2(A, 22); + const T2 = sigma0 + Maj2(A, B, C) | 0; + H = G; + G = F; + F = E; + E = D + T1 | 0; + D = C; + C = B; + B = A; + A = T1 + T2 | 0; + } + A = A + this.A | 0; + B = B + this.B | 0; + C = C + this.C | 0; + D = D + this.D | 0; + E = E + this.E | 0; + F = F + this.F | 0; + G = G + this.G | 0; + H = H + this.H | 0; + this.set(A, B, C, D, E, F, G, H); + } + roundClean() { + SHA256_W2.fill(0); + } + destroy() { + this.set(0, 0, 0, 0, 0, 0, 0, 0); + this.buffer.fill(0); + } + }; + var SHA224 = class extends SHA2562 { + constructor() { + super(); + this.A = 3238371032 | 0; + this.B = 914150663 | 0; + this.C = 812702999 | 0; + this.D = 4144912697 | 0; + this.E = 4290775857 | 0; + this.F = 1750603025 | 0; + this.G = 1694076839 | 0; + this.H = 3204075428 | 0; + this.outputLen = 28; + } + }; + var sha2562 = wrapConstructor2(() => new SHA2562()); + var sha224 = wrapConstructor2(() => new SHA224()); + + // utils.ts + var utils_exports2 = {}; + __export(utils_exports2, { + Queue: () => Queue, + QueueNode: () => QueueNode, + binarySearch: () => binarySearch, + bytesToHex: () => bytesToHex2, + hexToBytes: () => hexToBytes2, + insertEventIntoAscendingList: () => insertEventIntoAscendingList, + insertEventIntoDescendingList: () => insertEventIntoDescendingList, + normalizeURL: () => normalizeURL, + utf8Decoder: () => utf8Decoder, + utf8Encoder: () => utf8Encoder + }); + var utf8Decoder = new TextDecoder("utf-8"); + var utf8Encoder = new TextEncoder(); + function normalizeURL(url) { + try { + if (url.indexOf("://") === -1) + url = "wss://" + url; + let p = new URL(url); + p.pathname = p.pathname.replace(/\/+/g, "/"); + if (p.pathname.endsWith("/")) + p.pathname = p.pathname.slice(0, -1); + if (p.port === "80" && p.protocol === "ws:" || p.port === "443" && p.protocol === "wss:") + p.port = ""; + p.searchParams.sort(); + p.hash = ""; + return p.toString(); + } catch (e) { + throw new Error(`Invalid URL: ${url}`); + } + } + function insertEventIntoDescendingList(sortedArray, event) { + const [idx, found] = binarySearch(sortedArray, (b) => { + if (event.id === b.id) + return 0; + if (event.created_at === b.created_at) + return -1; + return b.created_at - event.created_at; + }); + if (!found) { + sortedArray.splice(idx, 0, event); + } + return sortedArray; + } + function insertEventIntoAscendingList(sortedArray, event) { + const [idx, found] = binarySearch(sortedArray, (b) => { + if (event.id === b.id) + return 0; + if (event.created_at === b.created_at) + return -1; + return event.created_at - b.created_at; + }); + if (!found) { + sortedArray.splice(idx, 0, event); + } + return sortedArray; + } + function binarySearch(arr, compare) { + let start = 0; + let end = arr.length - 1; + while (start <= end) { + const mid = Math.floor((start + end) / 2); + const cmp = compare(arr[mid]); + if (cmp === 0) { + return [mid, true]; + } + if (cmp < 0) { + end = mid - 1; + } else { + start = mid + 1; + } + } + return [start, false]; + } + var QueueNode = class { + value; + next = null; + prev = null; + constructor(message) { + this.value = message; + } + }; + var Queue = class { + first; + last; + constructor() { + this.first = null; + this.last = null; + } + enqueue(value) { + const newNode = new QueueNode(value); + if (!this.last) { + this.first = newNode; + this.last = newNode; + } else if (this.last === this.first) { + this.last = newNode; + this.last.prev = this.first; + this.first.next = newNode; + } else { + newNode.prev = this.last; + this.last.next = newNode; + this.last = newNode; + } + return true; + } + dequeue() { + if (!this.first) + return null; + if (this.first === this.last) { + const target2 = this.first; + this.first = null; + this.last = null; + return target2.value; + } + const target = this.first; + this.first = target.next; + if (this.first) { + this.first.prev = null; + } + return target.value; + } + }; + + // pure.ts + var JS = class { + generateSecretKey() { + return schnorr.utils.randomPrivateKey(); + } + getPublicKey(secretKey) { + return bytesToHex2(schnorr.getPublicKey(secretKey)); + } + finalizeEvent(t, secretKey) { + const event = t; + event.pubkey = bytesToHex2(schnorr.getPublicKey(secretKey)); + event.id = getEventHash(event); + event.sig = bytesToHex2(schnorr.sign(getEventHash(event), secretKey)); + event[verifiedSymbol] = true; + return event; + } + verifyEvent(event) { + if (typeof event[verifiedSymbol] === "boolean") + return event[verifiedSymbol]; + const hash3 = getEventHash(event); + if (hash3 !== event.id) { + event[verifiedSymbol] = false; + return false; + } + try { + const valid = schnorr.verify(event.sig, hash3, event.pubkey); + event[verifiedSymbol] = valid; + return valid; + } catch (err) { + event[verifiedSymbol] = false; + return false; + } + } + }; + function serializeEvent(evt) { + if (!validateEvent(evt)) + throw new Error("can't serialize event with wrong or missing properties"); + return JSON.stringify([0, evt.pubkey, evt.created_at, evt.kind, evt.tags, evt.content]); + } + function getEventHash(event) { + let eventHash = sha2562(utf8Encoder.encode(serializeEvent(event))); + return bytesToHex2(eventHash); + } + var i = new JS(); + var generateSecretKey = i.generateSecretKey; + var getPublicKey = i.getPublicKey; + var finalizeEvent = i.finalizeEvent; + var verifyEvent = i.verifyEvent; + + // kinds.ts + var kinds_exports = {}; + __export(kinds_exports, { + Application: () => Application, + BadgeAward: () => BadgeAward, + BadgeDefinition: () => BadgeDefinition, + BlockedRelaysList: () => BlockedRelaysList, + BookmarkList: () => BookmarkList, + Bookmarksets: () => Bookmarksets, + Calendar: () => Calendar, + CalendarEventRSVP: () => CalendarEventRSVP, + ChannelCreation: () => ChannelCreation, + ChannelHideMessage: () => ChannelHideMessage, + ChannelMessage: () => ChannelMessage, + ChannelMetadata: () => ChannelMetadata, + ChannelMuteUser: () => ChannelMuteUser, + ClassifiedListing: () => ClassifiedListing, + ClientAuth: () => ClientAuth, + CommunitiesList: () => CommunitiesList, + CommunityDefinition: () => CommunityDefinition, + CommunityPostApproval: () => CommunityPostApproval, + Contacts: () => Contacts, + CreateOrUpdateProduct: () => CreateOrUpdateProduct, + CreateOrUpdateStall: () => CreateOrUpdateStall, + Curationsets: () => Curationsets, + Date: () => Date2, + DirectMessageRelaysList: () => DirectMessageRelaysList, + DraftClassifiedListing: () => DraftClassifiedListing, + DraftLong: () => DraftLong, + Emojisets: () => Emojisets, + EncryptedDirectMessage: () => EncryptedDirectMessage, + EventDeletion: () => EventDeletion, + FileMetadata: () => FileMetadata, + FileServerPreference: () => FileServerPreference, + Followsets: () => Followsets, + GenericRepost: () => GenericRepost, + Genericlists: () => Genericlists, + GiftWrap: () => GiftWrap, + HTTPAuth: () => HTTPAuth, + Handlerinformation: () => Handlerinformation, + Handlerrecommendation: () => Handlerrecommendation, + Highlights: () => Highlights, + InterestsList: () => InterestsList, + Interestsets: () => Interestsets, + JobFeedback: () => JobFeedback, + JobRequest: () => JobRequest, + JobResult: () => JobResult, + Label: () => Label, + LightningPubRPC: () => LightningPubRPC, + LiveChatMessage: () => LiveChatMessage, + LiveEvent: () => LiveEvent, + LongFormArticle: () => LongFormArticle, + Metadata: () => Metadata, + Mutelist: () => Mutelist, + NWCWalletInfo: () => NWCWalletInfo, + NWCWalletRequest: () => NWCWalletRequest, + NWCWalletResponse: () => NWCWalletResponse, + NostrConnect: () => NostrConnect, + OpenTimestamps: () => OpenTimestamps, + Pinlist: () => Pinlist, + PrivateDirectMessage: () => PrivateDirectMessage, + ProblemTracker: () => ProblemTracker, + ProfileBadges: () => ProfileBadges, + PublicChatsList: () => PublicChatsList, + Reaction: () => Reaction, + RecommendRelay: () => RecommendRelay, + RelayList: () => RelayList, + Relaysets: () => Relaysets, + Report: () => Report, + Reporting: () => Reporting, + Repost: () => Repost, + Seal: () => Seal, + SearchRelaysList: () => SearchRelaysList, + ShortTextNote: () => ShortTextNote, + Time: () => Time, + UserEmojiList: () => UserEmojiList, + UserStatuses: () => UserStatuses, + Zap: () => Zap, + ZapGoal: () => ZapGoal, + ZapRequest: () => ZapRequest, + classifyKind: () => classifyKind, + isAddressableKind: () => isAddressableKind, + isEphemeralKind: () => isEphemeralKind, + isKind: () => isKind, + isRegularKind: () => isRegularKind, + isReplaceableKind: () => isReplaceableKind + }); + function isRegularKind(kind) { + return 1e3 <= kind && kind < 1e4 || [1, 2, 4, 5, 6, 7, 8, 16, 40, 41, 42, 43, 44].includes(kind); + } + function isReplaceableKind(kind) { + return [0, 3].includes(kind) || 1e4 <= kind && kind < 2e4; + } + function isEphemeralKind(kind) { + return 2e4 <= kind && kind < 3e4; + } + function isAddressableKind(kind) { + return 3e4 <= kind && kind < 4e4; + } + function classifyKind(kind) { + if (isRegularKind(kind)) + return "regular"; + if (isReplaceableKind(kind)) + return "replaceable"; + if (isEphemeralKind(kind)) + return "ephemeral"; + if (isAddressableKind(kind)) + return "parameterized"; + return "unknown"; + } + function isKind(event, kind) { + const kindAsArray = kind instanceof Array ? kind : [kind]; + return validateEvent(event) && kindAsArray.includes(event.kind) || false; + } + var Metadata = 0; + var ShortTextNote = 1; + var RecommendRelay = 2; + var Contacts = 3; + var EncryptedDirectMessage = 4; + var EventDeletion = 5; + var Repost = 6; + var Reaction = 7; + var BadgeAward = 8; + var Seal = 13; + var PrivateDirectMessage = 14; + var GenericRepost = 16; + var ChannelCreation = 40; + var ChannelMetadata = 41; + var ChannelMessage = 42; + var ChannelHideMessage = 43; + var ChannelMuteUser = 44; + var OpenTimestamps = 1040; + var GiftWrap = 1059; + var FileMetadata = 1063; + var LiveChatMessage = 1311; + var ProblemTracker = 1971; + var Report = 1984; + var Reporting = 1984; + var Label = 1985; + var CommunityPostApproval = 4550; + var JobRequest = 5999; + var JobResult = 6999; + var JobFeedback = 7e3; + var ZapGoal = 9041; + var ZapRequest = 9734; + var Zap = 9735; + var Highlights = 9802; + var Mutelist = 1e4; + var Pinlist = 10001; + var RelayList = 10002; + var BookmarkList = 10003; + var CommunitiesList = 10004; + var PublicChatsList = 10005; + var BlockedRelaysList = 10006; + var SearchRelaysList = 10007; + var InterestsList = 10015; + var UserEmojiList = 10030; + var DirectMessageRelaysList = 10050; + var FileServerPreference = 10096; + var NWCWalletInfo = 13194; + var LightningPubRPC = 21e3; + var ClientAuth = 22242; + var NWCWalletRequest = 23194; + var NWCWalletResponse = 23195; + var NostrConnect = 24133; + var HTTPAuth = 27235; + var Followsets = 3e4; + var Genericlists = 30001; + var Relaysets = 30002; + var Bookmarksets = 30003; + var Curationsets = 30004; + var ProfileBadges = 30008; + var BadgeDefinition = 30009; + var Interestsets = 30015; + var CreateOrUpdateStall = 30017; + var CreateOrUpdateProduct = 30018; + var LongFormArticle = 30023; + var DraftLong = 30024; + var Emojisets = 30030; + var Application = 30078; + var LiveEvent = 30311; + var UserStatuses = 30315; + var ClassifiedListing = 30402; + var DraftClassifiedListing = 30403; + var Date2 = 31922; + var Time = 31923; + var Calendar = 31924; + var CalendarEventRSVP = 31925; + var Handlerrecommendation = 31989; + var Handlerinformation = 31990; + var CommunityDefinition = 34550; + + // filter.ts + function matchFilter(filter, event) { + if (filter.ids && filter.ids.indexOf(event.id) === -1) { + return false; + } + if (filter.kinds && filter.kinds.indexOf(event.kind) === -1) { + return false; + } + if (filter.authors && filter.authors.indexOf(event.pubkey) === -1) { + return false; + } + for (let f in filter) { + if (f[0] === "#") { + let tagName = f.slice(1); + let values = filter[`#${tagName}`]; + if (values && !event.tags.find(([t, v]) => t === f.slice(1) && values.indexOf(v) !== -1)) + return false; + } + } + if (filter.since && event.created_at < filter.since) + return false; + if (filter.until && event.created_at > filter.until) + return false; + return true; + } + function matchFilters(filters, event) { + for (let i2 = 0; i2 < filters.length; i2++) { + if (matchFilter(filters[i2], event)) { + return true; + } + } + return false; + } + function mergeFilters(...filters) { + let result = {}; + for (let i2 = 0; i2 < filters.length; i2++) { + let filter = filters[i2]; + Object.entries(filter).forEach(([property, values]) => { + if (property === "kinds" || property === "ids" || property === "authors" || property[0] === "#") { + result[property] = result[property] || []; + for (let v = 0; v < values.length; v++) { + let value = values[v]; + if (!result[property].includes(value)) + result[property].push(value); + } + } + }); + if (filter.limit && (!result.limit || filter.limit > result.limit)) + result.limit = filter.limit; + if (filter.until && (!result.until || filter.until > result.until)) + result.until = filter.until; + if (filter.since && (!result.since || filter.since < result.since)) + result.since = filter.since; + } + return result; + } + function getFilterLimit(filter) { + if (filter.ids && !filter.ids.length) + return 0; + if (filter.kinds && !filter.kinds.length) + return 0; + if (filter.authors && !filter.authors.length) + return 0; + for (const [key, value] of Object.entries(filter)) { + if (key[0] === "#" && Array.isArray(value) && !value.length) + return 0; + } + return Math.min( + Math.max(0, filter.limit ?? Infinity), + filter.ids?.length ?? Infinity, + filter.authors?.length && filter.kinds?.every((kind) => isReplaceableKind(kind)) ? filter.authors.length * filter.kinds.length : Infinity, + filter.authors?.length && filter.kinds?.every((kind) => isAddressableKind(kind)) && filter["#d"]?.length ? filter.authors.length * filter.kinds.length * filter["#d"].length : Infinity + ); + } + + // fakejson.ts + var fakejson_exports = {}; + __export(fakejson_exports, { + getHex64: () => getHex64, + getInt: () => getInt, + getSubscriptionId: () => getSubscriptionId, + matchEventId: () => matchEventId, + matchEventKind: () => matchEventKind, + matchEventPubkey: () => matchEventPubkey + }); + function getHex64(json, field) { + let len = field.length + 3; + let idx = json.indexOf(`"${field}":`) + len; + let s = json.slice(idx).indexOf(`"`) + idx + 1; + return json.slice(s, s + 64); + } + function getInt(json, field) { + let len = field.length; + let idx = json.indexOf(`"${field}":`) + len + 3; + let sliced = json.slice(idx); + let end = Math.min(sliced.indexOf(","), sliced.indexOf("}")); + return parseInt(sliced.slice(0, end), 10); + } + function getSubscriptionId(json) { + let idx = json.slice(0, 22).indexOf(`"EVENT"`); + if (idx === -1) + return null; + let pstart = json.slice(idx + 7 + 1).indexOf(`"`); + if (pstart === -1) + return null; + let start = idx + 7 + 1 + pstart; + let pend = json.slice(start + 1, 80).indexOf(`"`); + if (pend === -1) + return null; + let end = start + 1 + pend; + return json.slice(start + 1, end); + } + function matchEventId(json, id) { + return id === getHex64(json, "id"); + } + function matchEventPubkey(json, pubkey) { + return pubkey === getHex64(json, "pubkey"); + } + function matchEventKind(json, kind) { + return kind === getInt(json, "kind"); + } + + // nip42.ts + var nip42_exports = {}; + __export(nip42_exports, { + makeAuthEvent: () => makeAuthEvent + }); + function makeAuthEvent(relayURL, challenge2) { + return { + kind: ClientAuth, + created_at: Math.floor(Date.now() / 1e3), + tags: [ + ["relay", relayURL], + ["challenge", challenge2] + ], + content: "" + }; + } + + // helpers.ts + async function yieldThread() { + return new Promise((resolve) => { + const ch = new MessageChannel(); + const handler = () => { + ch.port1.removeEventListener("message", handler); + resolve(); + }; + ch.port1.addEventListener("message", handler); + ch.port2.postMessage(0); + ch.port1.start(); + }); + } + var alwaysTrue = (t) => { + t[verifiedSymbol] = true; + return true; + }; + + // abstract-relay.ts + var SendingOnClosedConnection = class extends Error { + constructor(message, relay) { + super(`Tried to send message '${message} on a closed connection to ${relay}.`); + this.name = "SendingOnClosedConnection"; + } + }; + var AbstractRelay = class { + url; + _connected = false; + onclose = null; + onnotice = (msg) => console.debug(`NOTICE from ${this.url}: ${msg}`); + baseEoseTimeout = 4400; + connectionTimeout = 4400; + publishTimeout = 4400; + pingFrequency = 2e4; + pingTimeout = 2e4; + openSubs = /* @__PURE__ */ new Map(); + enablePing; + connectionTimeoutHandle; + connectionPromise; + openCountRequests = /* @__PURE__ */ new Map(); + openEventPublishes = /* @__PURE__ */ new Map(); + ws; + incomingMessageQueue = new Queue(); + queueRunning = false; + challenge; + authPromise; + serial = 0; + verifyEvent; + _WebSocket; + constructor(url, opts) { + this.url = normalizeURL(url); + this.verifyEvent = opts.verifyEvent; + this._WebSocket = opts.websocketImplementation || WebSocket; + this.enablePing = opts.enablePing; + } + static async connect(url, opts) { + const relay = new AbstractRelay(url, opts); + await relay.connect(); + return relay; + } + closeAllSubscriptions(reason) { + for (let [_, sub] of this.openSubs) { + sub.close(reason); + } + this.openSubs.clear(); + for (let [_, ep] of this.openEventPublishes) { + ep.reject(new Error(reason)); + } + this.openEventPublishes.clear(); + for (let [_, cr] of this.openCountRequests) { + cr.reject(new Error(reason)); + } + this.openCountRequests.clear(); + } + get connected() { + return this._connected; + } + async connect() { + if (this.connectionPromise) + return this.connectionPromise; + this.challenge = void 0; + this.authPromise = void 0; + this.connectionPromise = new Promise((resolve, reject) => { + this.connectionTimeoutHandle = setTimeout(() => { + reject("connection timed out"); + this.connectionPromise = void 0; + this.onclose?.(); + this.closeAllSubscriptions("relay connection timed out"); + }, this.connectionTimeout); + try { + this.ws = new this._WebSocket(this.url); + } catch (err) { + clearTimeout(this.connectionTimeoutHandle); + reject(err); + return; + } + this.ws.onopen = () => { + clearTimeout(this.connectionTimeoutHandle); + this._connected = true; + if (this.enablePing) { + this.pingpong(); + } + resolve(); + }; + this.ws.onerror = (ev) => { + clearTimeout(this.connectionTimeoutHandle); + reject(ev.message || "websocket error"); + if (this._connected) { + this._connected = false; + this.connectionPromise = void 0; + this.onclose?.(); + this.closeAllSubscriptions("relay connection errored"); + } + }; + this.ws.onclose = (ev) => { + clearTimeout(this.connectionTimeoutHandle); + reject(ev.message || "websocket closed"); + if (this._connected) { + this._connected = false; + this.connectionPromise = void 0; + this.onclose?.(); + this.closeAllSubscriptions("relay connection closed"); + } + }; + this.ws.onmessage = this._onmessage.bind(this); + }); + return this.connectionPromise; + } + async waitForPingPong() { + return new Promise((res, err) => { + ; + this.ws && this.ws.on && this.ws.on("pong", () => res(true)) || err("ws can't listen for pong"); + this.ws && this.ws.ping && this.ws.ping(); + }); + } + async waitForDummyReq() { + return new Promise((resolve, _) => { + const sub = this.subscribe([{ ids: ["a".repeat(64)] }], { + oneose: () => { + sub.close(); + resolve(true); + }, + eoseTimeout: this.pingTimeout + 1e3 + }); + }); + } + async pingpong() { + if (this.ws?.readyState === 1) { + const result = await Promise.any([ + this.ws && this.ws.ping && this.ws.on ? this.waitForPingPong() : this.waitForDummyReq(), + new Promise((res) => setTimeout(() => res(false), this.pingTimeout)) + ]); + if (result) { + setTimeout(() => this.pingpong(), this.pingFrequency); + } else { + this.closeAllSubscriptions("pingpong timed out"); + this._connected = false; + this.ws?.close(); + this.onclose?.(); + } + } + } + async runQueue() { + this.queueRunning = true; + while (true) { + if (false === this.handleNext()) { + break; + } + await yieldThread(); + } + this.queueRunning = false; + } + handleNext() { + const json = this.incomingMessageQueue.dequeue(); + if (!json) { + return false; + } + const subid = getSubscriptionId(json); + if (subid) { + const so = this.openSubs.get(subid); + if (!so) { + return; + } + const id = getHex64(json, "id"); + const alreadyHave = so.alreadyHaveEvent?.(id); + so.receivedEvent?.(this, id); + if (alreadyHave) { + return; + } + } + try { + let data = JSON.parse(json); + switch (data[0]) { + case "EVENT": { + const so = this.openSubs.get(data[1]); + const event = data[2]; + if (this.verifyEvent(event) && matchFilters(so.filters, event)) { + so.onevent(event); + } + return; + } + case "COUNT": { + const id = data[1]; + const payload = data[2]; + const cr = this.openCountRequests.get(id); + if (cr) { + cr.resolve(payload.count); + this.openCountRequests.delete(id); + } + return; + } + case "EOSE": { + const so = this.openSubs.get(data[1]); + if (!so) + return; + so.receivedEose(); + return; + } + case "OK": { + const id = data[1]; + const ok = data[2]; + const reason = data[3]; + const ep = this.openEventPublishes.get(id); + if (ep) { + clearTimeout(ep.timeout); + if (ok) + ep.resolve(reason); + else + ep.reject(new Error(reason)); + this.openEventPublishes.delete(id); + } + return; + } + case "CLOSED": { + const id = data[1]; + const so = this.openSubs.get(id); + if (!so) + return; + so.closed = true; + so.close(data[2]); + return; + } + case "NOTICE": + this.onnotice(data[1]); + return; + case "AUTH": { + this.challenge = data[1]; + return; + } + } + } catch (err) { + return; + } + } + async send(message) { + if (!this.connectionPromise) + throw new SendingOnClosedConnection(message, this.url); + this.connectionPromise.then(() => { + this.ws?.send(message); + }); + } + async auth(signAuthEvent) { + const challenge2 = this.challenge; + if (!challenge2) + throw new Error("can't perform auth, no challenge was received"); + if (this.authPromise) + return this.authPromise; + this.authPromise = new Promise(async (resolve, reject) => { + try { + let evt = await signAuthEvent(makeAuthEvent(this.url, challenge2)); + let timeout = setTimeout(() => { + let ep = this.openEventPublishes.get(evt.id); + if (ep) { + ep.reject(new Error("auth timed out")); + this.openEventPublishes.delete(evt.id); + } + }, this.publishTimeout); + this.openEventPublishes.set(evt.id, { resolve, reject, timeout }); + this.send('["AUTH",' + JSON.stringify(evt) + "]"); + } catch (err) { + console.warn("subscribe auth function failed:", err); + } + }); + return this.authPromise; + } + async publish(event) { + const ret = new Promise((resolve, reject) => { + const timeout = setTimeout(() => { + const ep = this.openEventPublishes.get(event.id); + if (ep) { + ep.reject(new Error("publish timed out")); + this.openEventPublishes.delete(event.id); + } + }, this.publishTimeout); + this.openEventPublishes.set(event.id, { resolve, reject, timeout }); + }); + this.send('["EVENT",' + JSON.stringify(event) + "]"); + return ret; + } + async count(filters, params) { + this.serial++; + const id = params?.id || "count:" + this.serial; + const ret = new Promise((resolve, reject) => { + this.openCountRequests.set(id, { resolve, reject }); + }); + this.send('["COUNT","' + id + '",' + JSON.stringify(filters).substring(1)); + return ret; + } + subscribe(filters, params) { + const subscription = this.prepareSubscription(filters, params); + subscription.fire(); + return subscription; + } + prepareSubscription(filters, params) { + this.serial++; + const id = params.id || (params.label ? params.label + ":" : "sub:") + this.serial; + const subscription = new Subscription(this, id, filters, params); + this.openSubs.set(id, subscription); + return subscription; + } + close() { + this.closeAllSubscriptions("relay connection closed by us"); + this._connected = false; + this.ws?.close(); + this.onclose?.(); + } + _onmessage(ev) { + this.incomingMessageQueue.enqueue(ev.data); + if (!this.queueRunning) { + this.runQueue(); + } + } + }; + var Subscription = class { + relay; + id; + closed = false; + eosed = false; + filters; + alreadyHaveEvent; + receivedEvent; + onevent; + oneose; + onclose; + eoseTimeout; + eoseTimeoutHandle; + constructor(relay, id, filters, params) { + this.relay = relay; + this.filters = filters; + this.id = id; + this.alreadyHaveEvent = params.alreadyHaveEvent; + this.receivedEvent = params.receivedEvent; + this.eoseTimeout = params.eoseTimeout || relay.baseEoseTimeout; + this.oneose = params.oneose; + this.onclose = params.onclose; + this.onevent = params.onevent || ((event) => { + console.warn( + `onevent() callback not defined for subscription '${this.id}' in relay ${this.relay.url}. event received:`, + event + ); + }); + } + fire() { + this.relay.send('["REQ","' + this.id + '",' + JSON.stringify(this.filters).substring(1)); + this.eoseTimeoutHandle = setTimeout(this.receivedEose.bind(this), this.eoseTimeout); + } + receivedEose() { + if (this.eosed) + return; + clearTimeout(this.eoseTimeoutHandle); + this.eosed = true; + this.oneose?.(); + } + close(reason = "closed by caller") { + if (!this.closed && this.relay.connected) { + try { + this.relay.send('["CLOSE",' + JSON.stringify(this.id) + "]"); + } catch (err) { + if (err instanceof SendingOnClosedConnection) { + } else { + throw err; + } + } + this.closed = true; + } + this.relay.openSubs.delete(this.id); + this.onclose?.(reason); + } + }; + + // relay.ts + var _WebSocket; + try { + _WebSocket = WebSocket; + } catch { + } + var Relay = class extends AbstractRelay { + constructor(url) { + super(url, { verifyEvent, websocketImplementation: _WebSocket }); + } + static async connect(url) { + const relay = new Relay(url); + await relay.connect(); + return relay; + } + }; + + // abstract-pool.ts + var AbstractSimplePool = class { + relays = /* @__PURE__ */ new Map(); + seenOn = /* @__PURE__ */ new Map(); + trackRelays = false; + verifyEvent; + enablePing; + trustedRelayURLs = /* @__PURE__ */ new Set(); + _WebSocket; + constructor(opts) { + this.verifyEvent = opts.verifyEvent; + this._WebSocket = opts.websocketImplementation; + this.enablePing = opts.enablePing; + } + async ensureRelay(url, params) { + url = normalizeURL(url); + let relay = this.relays.get(url); + if (!relay) { + relay = new AbstractRelay(url, { + verifyEvent: this.trustedRelayURLs.has(url) ? alwaysTrue : this.verifyEvent, + websocketImplementation: this._WebSocket, + enablePing: this.enablePing + }); + relay.onclose = () => { + this.relays.delete(url); + }; + if (params?.connectionTimeout) + relay.connectionTimeout = params.connectionTimeout; + this.relays.set(url, relay); + } + await relay.connect(); + return relay; + } + close(relays) { + relays.map(normalizeURL).forEach((url) => { + this.relays.get(url)?.close(); + this.relays.delete(url); + }); + } + subscribe(relays, filter, params) { + params.onauth = params.onauth || params.doauth; + const request = []; + for (let i2 = 0; i2 < relays.length; i2++) { + const url = normalizeURL(relays[i2]); + if (!request.find((r) => r.url === url)) { + request.push({ url, filter }); + } + } + return this.subscribeMap(request, params); + } + subscribeMany(relays, filters, params) { + params.onauth = params.onauth || params.doauth; + const request = []; + const uniqUrls = []; + for (let i2 = 0; i2 < relays.length; i2++) { + const url = normalizeURL(relays[i2]); + if (uniqUrls.indexOf(url) === -1) { + for (let f = 0; f < filters.length; f++) { + request.push({ url, filter: filters[f] }); + } + } + } + return this.subscribeMap(request, params); + } + subscribeMap(requests, params) { + params.onauth = params.onauth || params.doauth; + if (this.trackRelays) { + params.receivedEvent = (relay, id) => { + let set = this.seenOn.get(id); + if (!set) { + set = /* @__PURE__ */ new Set(); + this.seenOn.set(id, set); + } + set.add(relay); + }; + } + const _knownIds = /* @__PURE__ */ new Set(); + const subs = []; + const eosesReceived = []; + let handleEose = (i2) => { + if (eosesReceived[i2]) + return; + eosesReceived[i2] = true; + if (eosesReceived.filter((a) => a).length === requests.length) { + params.oneose?.(); + handleEose = () => { + }; + } + }; + const closesReceived = []; + let handleClose = (i2, reason) => { + if (closesReceived[i2]) + return; + handleEose(i2); + closesReceived[i2] = reason; + if (closesReceived.filter((a) => a).length === requests.length) { + params.onclose?.(closesReceived); + handleClose = () => { + }; + } + }; + const localAlreadyHaveEventHandler = (id) => { + if (params.alreadyHaveEvent?.(id)) { + return true; + } + const have = _knownIds.has(id); + _knownIds.add(id); + return have; + }; + const allOpened = Promise.all( + requests.map(async ({ url, filter }, i2) => { + let relay; + try { + relay = await this.ensureRelay(url, { + connectionTimeout: params.maxWait ? Math.max(params.maxWait * 0.8, params.maxWait - 1e3) : void 0 + }); + } catch (err) { + handleClose(i2, err?.message || String(err)); + return; + } + let subscription = relay.subscribe([filter], { + ...params, + oneose: () => handleEose(i2), + onclose: (reason) => { + if (reason.startsWith("auth-required: ") && params.onauth) { + relay.auth(params.onauth).then(() => { + relay.subscribe([filter], { + ...params, + oneose: () => handleEose(i2), + onclose: (reason2) => { + handleClose(i2, reason2); + }, + alreadyHaveEvent: localAlreadyHaveEventHandler, + eoseTimeout: params.maxWait + }); + }).catch((err) => { + handleClose(i2, `auth was required and attempted, but failed with: ${err}`); + }); + } else { + handleClose(i2, reason); + } + }, + alreadyHaveEvent: localAlreadyHaveEventHandler, + eoseTimeout: params.maxWait + }); + subs.push(subscription); + }) + ); + return { + async close(reason) { + await allOpened; + subs.forEach((sub) => { + sub.close(reason); + }); + } + }; + } + subscribeEose(relays, filter, params) { + params.onauth = params.onauth || params.doauth; + const subcloser = this.subscribe(relays, filter, { + ...params, + oneose() { + subcloser.close("closed automatically on eose"); + } + }); + return subcloser; + } + subscribeManyEose(relays, filters, params) { + params.onauth = params.onauth || params.doauth; + const subcloser = this.subscribeMany(relays, filters, { + ...params, + oneose() { + subcloser.close("closed automatically on eose"); + } + }); + return subcloser; + } + async querySync(relays, filter, params) { + return new Promise(async (resolve) => { + const events = []; + this.subscribeEose(relays, filter, { + ...params, + onevent(event) { + events.push(event); + }, + onclose(_) { + resolve(events); + } + }); + }); + } + async get(relays, filter, params) { + filter.limit = 1; + const events = await this.querySync(relays, filter, params); + events.sort((a, b) => b.created_at - a.created_at); + return events[0] || null; + } + publish(relays, event, options) { + return relays.map(normalizeURL).map(async (url, i2, arr) => { + if (arr.indexOf(url) !== i2) { + return Promise.reject("duplicate url"); + } + let r = await this.ensureRelay(url); + return r.publish(event).catch(async (err) => { + if (err instanceof Error && err.message.startsWith("auth-required: ") && options?.onauth) { + await r.auth(options.onauth); + return r.publish(event); + } + throw err; + }).then((reason) => { + if (this.trackRelays) { + let set = this.seenOn.get(event.id); + if (!set) { + set = /* @__PURE__ */ new Set(); + this.seenOn.set(event.id, set); + } + set.add(r); + } + return reason; + }); + }); + } + listConnectionStatus() { + const map = /* @__PURE__ */ new Map(); + this.relays.forEach((relay, url) => map.set(url, relay.connected)); + return map; + } + destroy() { + this.relays.forEach((conn) => conn.close()); + this.relays = /* @__PURE__ */ new Map(); + } + }; + + // pool.ts + var _WebSocket2; + try { + _WebSocket2 = WebSocket; + } catch { + } + var SimplePool = class extends AbstractSimplePool { + constructor(options) { + super({ verifyEvent, websocketImplementation: _WebSocket2, ...options }); + } + }; + + // nip19.ts + var nip19_exports = {}; + __export(nip19_exports, { + BECH32_REGEX: () => BECH32_REGEX, + Bech32MaxSize: () => Bech32MaxSize, + NostrTypeGuard: () => NostrTypeGuard, + decode: () => decode, + decodeNostrURI: () => decodeNostrURI, + encodeBytes: () => encodeBytes, + naddrEncode: () => naddrEncode, + neventEncode: () => neventEncode, + noteEncode: () => noteEncode, + nprofileEncode: () => nprofileEncode, + npubEncode: () => npubEncode, + nsecEncode: () => nsecEncode + }); + + // node_modules/@scure/base/lib/esm/index.js + function assertNumber(n) { + if (!Number.isSafeInteger(n)) + throw new Error(`Wrong integer: ${n}`); + } + function chain(...args) { + const wrap = (a, b) => (c) => a(b(c)); + const encode = Array.from(args).reverse().reduce((acc, i2) => acc ? wrap(acc, i2.encode) : i2.encode, void 0); + const decode2 = args.reduce((acc, i2) => acc ? wrap(acc, i2.decode) : i2.decode, void 0); + return { encode, decode: decode2 }; + } + function alphabet(alphabet2) { + return { + encode: (digits) => { + if (!Array.isArray(digits) || digits.length && typeof digits[0] !== "number") + throw new Error("alphabet.encode input should be an array of numbers"); + return digits.map((i2) => { + assertNumber(i2); + if (i2 < 0 || i2 >= alphabet2.length) + throw new Error(`Digit index outside alphabet: ${i2} (alphabet: ${alphabet2.length})`); + return alphabet2[i2]; + }); + }, + decode: (input) => { + if (!Array.isArray(input) || input.length && typeof input[0] !== "string") + throw new Error("alphabet.decode input should be array of strings"); + return input.map((letter) => { + if (typeof letter !== "string") + throw new Error(`alphabet.decode: not string element=${letter}`); + const index = alphabet2.indexOf(letter); + if (index === -1) + throw new Error(`Unknown letter: "${letter}". Allowed: ${alphabet2}`); + return index; + }); + } + }; + } + function join(separator = "") { + if (typeof separator !== "string") + throw new Error("join separator should be string"); + return { + encode: (from) => { + if (!Array.isArray(from) || from.length && typeof from[0] !== "string") + throw new Error("join.encode input should be array of strings"); + for (let i2 of from) + if (typeof i2 !== "string") + throw new Error(`join.encode: non-string input=${i2}`); + return from.join(separator); + }, + decode: (to) => { + if (typeof to !== "string") + throw new Error("join.decode input should be string"); + return to.split(separator); + } + }; + } + function padding(bits, chr = "=") { + assertNumber(bits); + if (typeof chr !== "string") + throw new Error("padding chr should be string"); + return { + encode(data) { + if (!Array.isArray(data) || data.length && typeof data[0] !== "string") + throw new Error("padding.encode input should be array of strings"); + for (let i2 of data) + if (typeof i2 !== "string") + throw new Error(`padding.encode: non-string input=${i2}`); + while (data.length * bits % 8) + data.push(chr); + return data; + }, + decode(input) { + if (!Array.isArray(input) || input.length && typeof input[0] !== "string") + throw new Error("padding.encode input should be array of strings"); + for (let i2 of input) + if (typeof i2 !== "string") + throw new Error(`padding.decode: non-string input=${i2}`); + let end = input.length; + if (end * bits % 8) + throw new Error("Invalid padding: string should have whole number of bytes"); + for (; end > 0 && input[end - 1] === chr; end--) { + if (!((end - 1) * bits % 8)) + throw new Error("Invalid padding: string has too much padding"); + } + return input.slice(0, end); + } + }; + } + function normalize(fn) { + if (typeof fn !== "function") + throw new Error("normalize fn should be function"); + return { encode: (from) => from, decode: (to) => fn(to) }; + } + function convertRadix(data, from, to) { + if (from < 2) + throw new Error(`convertRadix: wrong from=${from}, base cannot be less than 2`); + if (to < 2) + throw new Error(`convertRadix: wrong to=${to}, base cannot be less than 2`); + if (!Array.isArray(data)) + throw new Error("convertRadix: data should be array"); + if (!data.length) + return []; + let pos = 0; + const res = []; + const digits = Array.from(data); + digits.forEach((d) => { + assertNumber(d); + if (d < 0 || d >= from) + throw new Error(`Wrong integer: ${d}`); + }); + while (true) { + let carry = 0; + let done = true; + for (let i2 = pos; i2 < digits.length; i2++) { + const digit = digits[i2]; + const digitBase = from * carry + digit; + if (!Number.isSafeInteger(digitBase) || from * carry / from !== carry || digitBase - digit !== from * carry) { + throw new Error("convertRadix: carry overflow"); + } + carry = digitBase % to; + digits[i2] = Math.floor(digitBase / to); + if (!Number.isSafeInteger(digits[i2]) || digits[i2] * to + carry !== digitBase) + throw new Error("convertRadix: carry overflow"); + if (!done) + continue; + else if (!digits[i2]) + pos = i2; + else + done = false; + } + res.push(carry); + if (done) + break; + } + for (let i2 = 0; i2 < data.length - 1 && data[i2] === 0; i2++) + res.push(0); + return res.reverse(); + } + var gcd = (a, b) => !b ? a : gcd(b, a % b); + var radix2carry = (from, to) => from + (to - gcd(from, to)); + function convertRadix2(data, from, to, padding2) { + if (!Array.isArray(data)) + throw new Error("convertRadix2: data should be array"); + if (from <= 0 || from > 32) + throw new Error(`convertRadix2: wrong from=${from}`); + if (to <= 0 || to > 32) + throw new Error(`convertRadix2: wrong to=${to}`); + if (radix2carry(from, to) > 32) { + throw new Error(`convertRadix2: carry overflow from=${from} to=${to} carryBits=${radix2carry(from, to)}`); + } + let carry = 0; + let pos = 0; + const mask = 2 ** to - 1; + const res = []; + for (const n of data) { + assertNumber(n); + if (n >= 2 ** from) + throw new Error(`convertRadix2: invalid data word=${n} from=${from}`); + carry = carry << from | n; + if (pos + from > 32) + throw new Error(`convertRadix2: carry overflow pos=${pos} from=${from}`); + pos += from; + for (; pos >= to; pos -= to) + res.push((carry >> pos - to & mask) >>> 0); + carry &= 2 ** pos - 1; + } + carry = carry << to - pos & mask; + if (!padding2 && pos >= from) + throw new Error("Excess padding"); + if (!padding2 && carry) + throw new Error(`Non-zero padding: ${carry}`); + if (padding2 && pos > 0) + res.push(carry >>> 0); + return res; + } + function radix(num) { + assertNumber(num); + return { + encode: (bytes4) => { + if (!(bytes4 instanceof Uint8Array)) + throw new Error("radix.encode input should be Uint8Array"); + return convertRadix(Array.from(bytes4), 2 ** 8, num); + }, + decode: (digits) => { + if (!Array.isArray(digits) || digits.length && typeof digits[0] !== "number") + throw new Error("radix.decode input should be array of strings"); + return Uint8Array.from(convertRadix(digits, num, 2 ** 8)); + } + }; + } + function radix2(bits, revPadding = false) { + assertNumber(bits); + if (bits <= 0 || bits > 32) + throw new Error("radix2: bits should be in (0..32]"); + if (radix2carry(8, bits) > 32 || radix2carry(bits, 8) > 32) + throw new Error("radix2: carry overflow"); + return { + encode: (bytes4) => { + if (!(bytes4 instanceof Uint8Array)) + throw new Error("radix2.encode input should be Uint8Array"); + return convertRadix2(Array.from(bytes4), 8, bits, !revPadding); + }, + decode: (digits) => { + if (!Array.isArray(digits) || digits.length && typeof digits[0] !== "number") + throw new Error("radix2.decode input should be array of strings"); + return Uint8Array.from(convertRadix2(digits, bits, 8, revPadding)); + } + }; + } + function unsafeWrapper(fn) { + if (typeof fn !== "function") + throw new Error("unsafeWrapper fn should be function"); + return function(...args) { + try { + return fn.apply(null, args); + } catch (e) { + } + }; + } + var base16 = chain(radix2(4), alphabet("0123456789ABCDEF"), join("")); + var base32 = chain(radix2(5), alphabet("ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"), padding(5), join("")); + var base32hex = chain(radix2(5), alphabet("0123456789ABCDEFGHIJKLMNOPQRSTUV"), padding(5), join("")); + var base32crockford = chain(radix2(5), alphabet("0123456789ABCDEFGHJKMNPQRSTVWXYZ"), join(""), normalize((s) => s.toUpperCase().replace(/O/g, "0").replace(/[IL]/g, "1"))); + var base64 = chain(radix2(6), alphabet("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"), padding(6), join("")); + var base64url = chain(radix2(6), alphabet("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"), padding(6), join("")); + var genBase58 = (abc) => chain(radix(58), alphabet(abc), join("")); + var base58 = genBase58("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"); + var base58flickr = genBase58("123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ"); + var base58xrp = genBase58("rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz"); + var XMR_BLOCK_LEN = [0, 2, 3, 5, 6, 7, 9, 10, 11]; + var base58xmr = { + encode(data) { + let res = ""; + for (let i2 = 0; i2 < data.length; i2 += 8) { + const block = data.subarray(i2, i2 + 8); + res += base58.encode(block).padStart(XMR_BLOCK_LEN[block.length], "1"); + } + return res; + }, + decode(str) { + let res = []; + for (let i2 = 0; i2 < str.length; i2 += 11) { + const slice = str.slice(i2, i2 + 11); + const blockLen = XMR_BLOCK_LEN.indexOf(slice.length); + const block = base58.decode(slice); + for (let j = 0; j < block.length - blockLen; j++) { + if (block[j] !== 0) + throw new Error("base58xmr: wrong padding"); + } + res = res.concat(Array.from(block.slice(block.length - blockLen))); + } + return Uint8Array.from(res); + } + }; + var BECH_ALPHABET = chain(alphabet("qpzry9x8gf2tvdw0s3jn54khce6mua7l"), join("")); + var POLYMOD_GENERATORS = [996825010, 642813549, 513874426, 1027748829, 705979059]; + function bech32Polymod(pre) { + const b = pre >> 25; + let chk = (pre & 33554431) << 5; + for (let i2 = 0; i2 < POLYMOD_GENERATORS.length; i2++) { + if ((b >> i2 & 1) === 1) + chk ^= POLYMOD_GENERATORS[i2]; + } + return chk; + } + function bechChecksum(prefix, words, encodingConst = 1) { + const len = prefix.length; + let chk = 1; + for (let i2 = 0; i2 < len; i2++) { + const c = prefix.charCodeAt(i2); + if (c < 33 || c > 126) + throw new Error(`Invalid prefix (${prefix})`); + chk = bech32Polymod(chk) ^ c >> 5; + } + chk = bech32Polymod(chk); + for (let i2 = 0; i2 < len; i2++) + chk = bech32Polymod(chk) ^ prefix.charCodeAt(i2) & 31; + for (let v of words) + chk = bech32Polymod(chk) ^ v; + for (let i2 = 0; i2 < 6; i2++) + chk = bech32Polymod(chk); + chk ^= encodingConst; + return BECH_ALPHABET.encode(convertRadix2([chk % 2 ** 30], 30, 5, false)); + } + function genBech32(encoding) { + const ENCODING_CONST = encoding === "bech32" ? 1 : 734539939; + const _words = radix2(5); + const fromWords = _words.decode; + const toWords = _words.encode; + const fromWordsUnsafe = unsafeWrapper(fromWords); + function encode(prefix, words, limit2 = 90) { + if (typeof prefix !== "string") + throw new Error(`bech32.encode prefix should be string, not ${typeof prefix}`); + if (!Array.isArray(words) || words.length && typeof words[0] !== "number") + throw new Error(`bech32.encode words should be array of numbers, not ${typeof words}`); + const actualLength = prefix.length + 7 + words.length; + if (limit2 !== false && actualLength > limit2) + throw new TypeError(`Length ${actualLength} exceeds limit ${limit2}`); + prefix = prefix.toLowerCase(); + return `${prefix}1${BECH_ALPHABET.encode(words)}${bechChecksum(prefix, words, ENCODING_CONST)}`; + } + function decode2(str, limit2 = 90) { + if (typeof str !== "string") + throw new Error(`bech32.decode input should be string, not ${typeof str}`); + if (str.length < 8 || limit2 !== false && str.length > limit2) + throw new TypeError(`Wrong string length: ${str.length} (${str}). Expected (8..${limit2})`); + const lowered = str.toLowerCase(); + if (str !== lowered && str !== str.toUpperCase()) + throw new Error(`String must be lowercase or uppercase`); + str = lowered; + const sepIndex = str.lastIndexOf("1"); + if (sepIndex === 0 || sepIndex === -1) + throw new Error(`Letter "1" must be present between prefix and data only`); + const prefix = str.slice(0, sepIndex); + const _words2 = str.slice(sepIndex + 1); + if (_words2.length < 6) + throw new Error("Data must be at least 6 characters long"); + const words = BECH_ALPHABET.decode(_words2).slice(0, -6); + const sum = bechChecksum(prefix, words, ENCODING_CONST); + if (!_words2.endsWith(sum)) + throw new Error(`Invalid checksum in ${str}: expected "${sum}"`); + return { prefix, words }; + } + const decodeUnsafe = unsafeWrapper(decode2); + function decodeToBytes(str) { + const { prefix, words } = decode2(str, false); + return { prefix, words, bytes: fromWords(words) }; + } + return { encode, decode: decode2, decodeToBytes, decodeUnsafe, fromWords, fromWordsUnsafe, toWords }; + } + var bech32 = genBech32("bech32"); + var bech32m = genBech32("bech32m"); + var utf8 = { + encode: (data) => new TextDecoder().decode(data), + decode: (str) => new TextEncoder().encode(str) + }; + var hex = chain(radix2(4), alphabet("0123456789abcdef"), join(""), normalize((s) => { + if (typeof s !== "string" || s.length % 2) + throw new TypeError(`hex.decode: expected string, got ${typeof s} with length ${s.length}`); + return s.toLowerCase(); + })); + var CODERS = { + utf8, + hex, + base16, + base32, + base64, + base64url, + base58, + base58xmr + }; + var coderTypeError = `Invalid encoding type. Available types: ${Object.keys(CODERS).join(", ")}`; + + // nip19.ts + var NostrTypeGuard = { + isNProfile: (value) => /^nprofile1[a-z\d]+$/.test(value || ""), + isNEvent: (value) => /^nevent1[a-z\d]+$/.test(value || ""), + isNAddr: (value) => /^naddr1[a-z\d]+$/.test(value || ""), + isNSec: (value) => /^nsec1[a-z\d]{58}$/.test(value || ""), + isNPub: (value) => /^npub1[a-z\d]{58}$/.test(value || ""), + isNote: (value) => /^note1[a-z\d]+$/.test(value || ""), + isNcryptsec: (value) => /^ncryptsec1[a-z\d]+$/.test(value || "") + }; + var Bech32MaxSize = 5e3; + var BECH32_REGEX = /[\x21-\x7E]{1,83}1[023456789acdefghjklmnpqrstuvwxyz]{6,}/; + function integerToUint8Array(number4) { + const uint8Array = new Uint8Array(4); + uint8Array[0] = number4 >> 24 & 255; + uint8Array[1] = number4 >> 16 & 255; + uint8Array[2] = number4 >> 8 & 255; + uint8Array[3] = number4 & 255; + return uint8Array; + } + function decodeNostrURI(nip19code) { + try { + if (nip19code.startsWith("nostr:")) + nip19code = nip19code.substring(6); + return decode(nip19code); + } catch (_err) { + return { type: "invalid", data: null }; + } + } + function decode(code) { + let { prefix, words } = bech32.decode(code, Bech32MaxSize); + let data = new Uint8Array(bech32.fromWords(words)); + switch (prefix) { + case "nprofile": { + let tlv = parseTLV(data); + if (!tlv[0]?.[0]) + throw new Error("missing TLV 0 for nprofile"); + if (tlv[0][0].length !== 32) + throw new Error("TLV 0 should be 32 bytes"); + return { + type: "nprofile", + data: { + pubkey: bytesToHex2(tlv[0][0]), + relays: tlv[1] ? tlv[1].map((d) => utf8Decoder.decode(d)) : [] + } + }; + } + case "nevent": { + let tlv = parseTLV(data); + if (!tlv[0]?.[0]) + throw new Error("missing TLV 0 for nevent"); + if (tlv[0][0].length !== 32) + throw new Error("TLV 0 should be 32 bytes"); + if (tlv[2] && tlv[2][0].length !== 32) + throw new Error("TLV 2 should be 32 bytes"); + if (tlv[3] && tlv[3][0].length !== 4) + throw new Error("TLV 3 should be 4 bytes"); + return { + type: "nevent", + data: { + id: bytesToHex2(tlv[0][0]), + relays: tlv[1] ? tlv[1].map((d) => utf8Decoder.decode(d)) : [], + author: tlv[2]?.[0] ? bytesToHex2(tlv[2][0]) : void 0, + kind: tlv[3]?.[0] ? parseInt(bytesToHex2(tlv[3][0]), 16) : void 0 + } + }; + } + case "naddr": { + let tlv = parseTLV(data); + if (!tlv[0]?.[0]) + throw new Error("missing TLV 0 for naddr"); + if (!tlv[2]?.[0]) + throw new Error("missing TLV 2 for naddr"); + if (tlv[2][0].length !== 32) + throw new Error("TLV 2 should be 32 bytes"); + if (!tlv[3]?.[0]) + throw new Error("missing TLV 3 for naddr"); + if (tlv[3][0].length !== 4) + throw new Error("TLV 3 should be 4 bytes"); + return { + type: "naddr", + data: { + identifier: utf8Decoder.decode(tlv[0][0]), + pubkey: bytesToHex2(tlv[2][0]), + kind: parseInt(bytesToHex2(tlv[3][0]), 16), + relays: tlv[1] ? tlv[1].map((d) => utf8Decoder.decode(d)) : [] + } + }; + } + case "nsec": + return { type: prefix, data }; + case "npub": + case "note": + return { type: prefix, data: bytesToHex2(data) }; + default: + throw new Error(`unknown prefix ${prefix}`); + } + } + function parseTLV(data) { + let result = {}; + let rest = data; + while (rest.length > 0) { + let t = rest[0]; + let l = rest[1]; + let v = rest.slice(2, 2 + l); + rest = rest.slice(2 + l); + if (v.length < l) + throw new Error(`not enough data to read on TLV ${t}`); + result[t] = result[t] || []; + result[t].push(v); + } + return result; + } + function nsecEncode(key) { + return encodeBytes("nsec", key); + } + function npubEncode(hex2) { + return encodeBytes("npub", hexToBytes2(hex2)); + } + function noteEncode(hex2) { + return encodeBytes("note", hexToBytes2(hex2)); + } + function encodeBech32(prefix, data) { + let words = bech32.toWords(data); + return bech32.encode(prefix, words, Bech32MaxSize); + } + function encodeBytes(prefix, bytes4) { + return encodeBech32(prefix, bytes4); + } + function nprofileEncode(profile) { + let data = encodeTLV({ + 0: [hexToBytes2(profile.pubkey)], + 1: (profile.relays || []).map((url) => utf8Encoder.encode(url)) + }); + return encodeBech32("nprofile", data); + } + function neventEncode(event) { + let kindArray; + if (event.kind !== void 0) { + kindArray = integerToUint8Array(event.kind); + } + let data = encodeTLV({ + 0: [hexToBytes2(event.id)], + 1: (event.relays || []).map((url) => utf8Encoder.encode(url)), + 2: event.author ? [hexToBytes2(event.author)] : [], + 3: kindArray ? [new Uint8Array(kindArray)] : [] + }); + return encodeBech32("nevent", data); + } + function naddrEncode(addr) { + let kind = new ArrayBuffer(4); + new DataView(kind).setUint32(0, addr.kind, false); + let data = encodeTLV({ + 0: [utf8Encoder.encode(addr.identifier)], + 1: (addr.relays || []).map((url) => utf8Encoder.encode(url)), + 2: [hexToBytes2(addr.pubkey)], + 3: [new Uint8Array(kind)] + }); + return encodeBech32("naddr", data); + } + function encodeTLV(tlv) { + let entries = []; + Object.entries(tlv).reverse().forEach(([t, vs]) => { + vs.forEach((v) => { + let entry = new Uint8Array(v.length + 2); + entry.set([parseInt(t)], 0); + entry.set([v.length], 1); + entry.set(v, 2); + entries.push(entry); + }); + }); + return concatBytes3(...entries); + } + + // references.ts + var mentionRegex = /\bnostr:((note|npub|naddr|nevent|nprofile)1\w+)\b|#\[(\d+)\]/g; + function parseReferences(evt) { + let references = []; + for (let ref of evt.content.matchAll(mentionRegex)) { + if (ref[2]) { + try { + let { type, data } = decode(ref[1]); + switch (type) { + case "npub": { + references.push({ + text: ref[0], + profile: { pubkey: data, relays: [] } + }); + break; + } + case "nprofile": { + references.push({ + text: ref[0], + profile: data + }); + break; + } + case "note": { + references.push({ + text: ref[0], + event: { id: data, relays: [] } + }); + break; + } + case "nevent": { + references.push({ + text: ref[0], + event: data + }); + break; + } + case "naddr": { + references.push({ + text: ref[0], + address: data + }); + break; + } + } + } catch (err) { + } + } else if (ref[3]) { + let idx = parseInt(ref[3], 10); + let tag = evt.tags[idx]; + if (!tag) + continue; + switch (tag[0]) { + case "p": { + references.push({ + text: ref[0], + profile: { pubkey: tag[1], relays: tag[2] ? [tag[2]] : [] } + }); + break; + } + case "e": { + references.push({ + text: ref[0], + event: { id: tag[1], relays: tag[2] ? [tag[2]] : [] } + }); + break; + } + case "a": { + try { + let [kind, pubkey, identifier] = tag[1].split(":"); + references.push({ + text: ref[0], + address: { + identifier, + pubkey, + kind: parseInt(kind, 10), + relays: tag[2] ? [tag[2]] : [] + } + }); + } catch (err) { + } + break; + } + } + } + } + return references; + } + + // nip04.ts + var nip04_exports = {}; + __export(nip04_exports, { + decrypt: () => decrypt2, + encrypt: () => encrypt2 + }); + + // node_modules/@noble/ciphers/esm/_assert.js + function number3(n) { + if (!Number.isSafeInteger(n) || n < 0) + throw new Error(`positive integer expected, not ${n}`); + } + function bool2(b) { + if (typeof b !== "boolean") + throw new Error(`boolean expected, not ${b}`); + } + function isBytes(a) { + return a instanceof Uint8Array || a != null && typeof a === "object" && a.constructor.name === "Uint8Array"; + } + function bytes3(b, ...lengths) { + if (!isBytes(b)) + throw new Error("Uint8Array expected"); + if (lengths.length > 0 && !lengths.includes(b.length)) + throw new Error(`Uint8Array expected of length ${lengths}, not of length=${b.length}`); + } + function exists3(instance, checkFinished = true) { + if (instance.destroyed) + throw new Error("Hash instance has been destroyed"); + if (checkFinished && instance.finished) + throw new Error("Hash#digest() has already been called"); + } + function output3(out, instance) { + bytes3(out); + const min = instance.outputLen; + if (out.length < min) { + throw new Error(`digestInto() expects output buffer of length at least ${min}`); + } + } + + // node_modules/@noble/ciphers/esm/utils.js + var u8 = (arr) => new Uint8Array(arr.buffer, arr.byteOffset, arr.byteLength); + var u32 = (arr) => new Uint32Array(arr.buffer, arr.byteOffset, Math.floor(arr.byteLength / 4)); + var createView3 = (arr) => new DataView(arr.buffer, arr.byteOffset, arr.byteLength); + var isLE3 = new Uint8Array(new Uint32Array([287454020]).buffer)[0] === 68; + if (!isLE3) + throw new Error("Non little-endian hardware is not supported"); + function utf8ToBytes4(str) { + if (typeof str !== "string") + throw new Error(`string expected, got ${typeof str}`); + return new Uint8Array(new TextEncoder().encode(str)); + } + function toBytes3(data) { + if (typeof data === "string") + data = utf8ToBytes4(data); + else if (isBytes(data)) + data = data.slice(); + else + throw new Error(`Uint8Array expected, got ${typeof data}`); + return data; + } + function checkOpts(defaults, opts) { + if (opts == null || typeof opts !== "object") + throw new Error("options must be defined"); + const merged = Object.assign(defaults, opts); + return merged; + } + function equalBytes2(a, b) { + if (a.length !== b.length) + return false; + let diff = 0; + for (let i2 = 0; i2 < a.length; i2++) + diff |= a[i2] ^ b[i2]; + return diff === 0; + } + var wrapCipher = (params, c) => { + Object.assign(c, params); + return c; + }; + function setBigUint643(view, byteOffset, value, isLE4) { + if (typeof view.setBigUint64 === "function") + return view.setBigUint64(byteOffset, value, isLE4); + const _32n = BigInt(32); + const _u32_max = BigInt(4294967295); + const wh = Number(value >> _32n & _u32_max); + const wl = Number(value & _u32_max); + const h = isLE4 ? 4 : 0; + const l = isLE4 ? 0 : 4; + view.setUint32(byteOffset + h, wh, isLE4); + view.setUint32(byteOffset + l, wl, isLE4); + } + + // node_modules/@noble/ciphers/esm/_polyval.js + var BLOCK_SIZE = 16; + var ZEROS16 = /* @__PURE__ */ new Uint8Array(16); + var ZEROS32 = u32(ZEROS16); + var POLY = 225; + var mul2 = (s0, s1, s2, s3) => { + const hiBit = s3 & 1; + return { + s3: s2 << 31 | s3 >>> 1, + s2: s1 << 31 | s2 >>> 1, + s1: s0 << 31 | s1 >>> 1, + s0: s0 >>> 1 ^ POLY << 24 & -(hiBit & 1) + }; + }; + var swapLE = (n) => (n >>> 0 & 255) << 24 | (n >>> 8 & 255) << 16 | (n >>> 16 & 255) << 8 | n >>> 24 & 255 | 0; + function _toGHASHKey(k) { + k.reverse(); + const hiBit = k[15] & 1; + let carry = 0; + for (let i2 = 0; i2 < k.length; i2++) { + const t = k[i2]; + k[i2] = t >>> 1 | carry; + carry = (t & 1) << 7; + } + k[0] ^= -hiBit & 225; + return k; + } + var estimateWindow = (bytes4) => { + if (bytes4 > 64 * 1024) + return 8; + if (bytes4 > 1024) + return 4; + return 2; + }; + var GHASH = class { + constructor(key, expectedLength) { + this.blockLen = BLOCK_SIZE; + this.outputLen = BLOCK_SIZE; + this.s0 = 0; + this.s1 = 0; + this.s2 = 0; + this.s3 = 0; + this.finished = false; + key = toBytes3(key); + bytes3(key, 16); + const kView = createView3(key); + let k0 = kView.getUint32(0, false); + let k1 = kView.getUint32(4, false); + let k2 = kView.getUint32(8, false); + let k3 = kView.getUint32(12, false); + const doubles = []; + for (let i2 = 0; i2 < 128; i2++) { + doubles.push({ s0: swapLE(k0), s1: swapLE(k1), s2: swapLE(k2), s3: swapLE(k3) }); + ({ s0: k0, s1: k1, s2: k2, s3: k3 } = mul2(k0, k1, k2, k3)); + } + const W = estimateWindow(expectedLength || 1024); + if (![1, 2, 4, 8].includes(W)) + throw new Error(`ghash: wrong window size=${W}, should be 2, 4 or 8`); + this.W = W; + const bits = 128; + const windows = bits / W; + const windowSize = this.windowSize = 2 ** W; + const items = []; + for (let w = 0; w < windows; w++) { + for (let byte = 0; byte < windowSize; byte++) { + let s0 = 0, s1 = 0, s2 = 0, s3 = 0; + for (let j = 0; j < W; j++) { + const bit = byte >>> W - j - 1 & 1; + if (!bit) + continue; + const { s0: d0, s1: d1, s2: d2, s3: d3 } = doubles[W * w + j]; + s0 ^= d0, s1 ^= d1, s2 ^= d2, s3 ^= d3; + } + items.push({ s0, s1, s2, s3 }); + } + } + this.t = items; + } + _updateBlock(s0, s1, s2, s3) { + s0 ^= this.s0, s1 ^= this.s1, s2 ^= this.s2, s3 ^= this.s3; + const { W, t, windowSize } = this; + let o0 = 0, o1 = 0, o2 = 0, o3 = 0; + const mask = (1 << W) - 1; + let w = 0; + for (const num of [s0, s1, s2, s3]) { + for (let bytePos = 0; bytePos < 4; bytePos++) { + const byte = num >>> 8 * bytePos & 255; + for (let bitPos = 8 / W - 1; bitPos >= 0; bitPos--) { + const bit = byte >>> W * bitPos & mask; + const { s0: e0, s1: e1, s2: e2, s3: e3 } = t[w * windowSize + bit]; + o0 ^= e0, o1 ^= e1, o2 ^= e2, o3 ^= e3; + w += 1; + } + } + } + this.s0 = o0; + this.s1 = o1; + this.s2 = o2; + this.s3 = o3; + } + update(data) { + data = toBytes3(data); + exists3(this); + const b32 = u32(data); + const blocks = Math.floor(data.length / BLOCK_SIZE); + const left = data.length % BLOCK_SIZE; + for (let i2 = 0; i2 < blocks; i2++) { + this._updateBlock(b32[i2 * 4 + 0], b32[i2 * 4 + 1], b32[i2 * 4 + 2], b32[i2 * 4 + 3]); + } + if (left) { + ZEROS16.set(data.subarray(blocks * BLOCK_SIZE)); + this._updateBlock(ZEROS32[0], ZEROS32[1], ZEROS32[2], ZEROS32[3]); + ZEROS32.fill(0); + } + return this; + } + destroy() { + const { t } = this; + for (const elm of t) { + elm.s0 = 0, elm.s1 = 0, elm.s2 = 0, elm.s3 = 0; + } + } + digestInto(out) { + exists3(this); + output3(out, this); + this.finished = true; + const { s0, s1, s2, s3 } = this; + const o32 = u32(out); + o32[0] = s0; + o32[1] = s1; + o32[2] = s2; + o32[3] = s3; + return out; + } + digest() { + const res = new Uint8Array(BLOCK_SIZE); + this.digestInto(res); + this.destroy(); + return res; + } + }; + var Polyval = class extends GHASH { + constructor(key, expectedLength) { + key = toBytes3(key); + const ghKey = _toGHASHKey(key.slice()); + super(ghKey, expectedLength); + ghKey.fill(0); + } + update(data) { + data = toBytes3(data); + exists3(this); + const b32 = u32(data); + const left = data.length % BLOCK_SIZE; + const blocks = Math.floor(data.length / BLOCK_SIZE); + for (let i2 = 0; i2 < blocks; i2++) { + this._updateBlock(swapLE(b32[i2 * 4 + 3]), swapLE(b32[i2 * 4 + 2]), swapLE(b32[i2 * 4 + 1]), swapLE(b32[i2 * 4 + 0])); + } + if (left) { + ZEROS16.set(data.subarray(blocks * BLOCK_SIZE)); + this._updateBlock(swapLE(ZEROS32[3]), swapLE(ZEROS32[2]), swapLE(ZEROS32[1]), swapLE(ZEROS32[0])); + ZEROS32.fill(0); + } + return this; + } + digestInto(out) { + exists3(this); + output3(out, this); + this.finished = true; + const { s0, s1, s2, s3 } = this; + const o32 = u32(out); + o32[0] = s0; + o32[1] = s1; + o32[2] = s2; + o32[3] = s3; + return out.reverse(); + } + }; + function wrapConstructorWithKey(hashCons) { + const hashC = (msg, key) => hashCons(key, msg.length).update(toBytes3(msg)).digest(); + const tmp = hashCons(new Uint8Array(16), 0); + hashC.outputLen = tmp.outputLen; + hashC.blockLen = tmp.blockLen; + hashC.create = (key, expectedLength) => hashCons(key, expectedLength); + return hashC; + } + var ghash = wrapConstructorWithKey((key, expectedLength) => new GHASH(key, expectedLength)); + var polyval = wrapConstructorWithKey((key, expectedLength) => new Polyval(key, expectedLength)); + + // node_modules/@noble/ciphers/esm/aes.js + var BLOCK_SIZE2 = 16; + var BLOCK_SIZE32 = 4; + var EMPTY_BLOCK = new Uint8Array(BLOCK_SIZE2); + var POLY2 = 283; + function mul22(n) { + return n << 1 ^ POLY2 & -(n >> 7); + } + function mul(a, b) { + let res = 0; + for (; b > 0; b >>= 1) { + res ^= a & -(b & 1); + a = mul22(a); + } + return res; + } + var sbox = /* @__PURE__ */ (() => { + let t = new Uint8Array(256); + for (let i2 = 0, x = 1; i2 < 256; i2++, x ^= mul22(x)) + t[i2] = x; + const box = new Uint8Array(256); + box[0] = 99; + for (let i2 = 0; i2 < 255; i2++) { + let x = t[255 - i2]; + x |= x << 8; + box[t[i2]] = (x ^ x >> 4 ^ x >> 5 ^ x >> 6 ^ x >> 7 ^ 99) & 255; + } + return box; + })(); + var invSbox = /* @__PURE__ */ sbox.map((_, j) => sbox.indexOf(j)); + var rotr32_8 = (n) => n << 24 | n >>> 8; + var rotl32_8 = (n) => n << 8 | n >>> 24; + function genTtable(sbox2, fn) { + if (sbox2.length !== 256) + throw new Error("Wrong sbox length"); + const T0 = new Uint32Array(256).map((_, j) => fn(sbox2[j])); + const T1 = T0.map(rotl32_8); + const T2 = T1.map(rotl32_8); + const T3 = T2.map(rotl32_8); + const T01 = new Uint32Array(256 * 256); + const T23 = new Uint32Array(256 * 256); + const sbox22 = new Uint16Array(256 * 256); + for (let i2 = 0; i2 < 256; i2++) { + for (let j = 0; j < 256; j++) { + const idx = i2 * 256 + j; + T01[idx] = T0[i2] ^ T1[j]; + T23[idx] = T2[i2] ^ T3[j]; + sbox22[idx] = sbox2[i2] << 8 | sbox2[j]; + } + } + return { sbox: sbox2, sbox2: sbox22, T0, T1, T2, T3, T01, T23 }; + } + var tableEncoding = /* @__PURE__ */ genTtable(sbox, (s) => mul(s, 3) << 24 | s << 16 | s << 8 | mul(s, 2)); + var tableDecoding = /* @__PURE__ */ genTtable(invSbox, (s) => mul(s, 11) << 24 | mul(s, 13) << 16 | mul(s, 9) << 8 | mul(s, 14)); + var xPowers = /* @__PURE__ */ (() => { + const p = new Uint8Array(16); + for (let i2 = 0, x = 1; i2 < 16; i2++, x = mul22(x)) + p[i2] = x; + return p; + })(); + function expandKeyLE(key) { + bytes3(key); + const len = key.length; + if (![16, 24, 32].includes(len)) + throw new Error(`aes: wrong key size: should be 16, 24 or 32, got: ${len}`); + const { sbox2 } = tableEncoding; + const k32 = u32(key); + const Nk = k32.length; + const subByte = (n) => applySbox(sbox2, n, n, n, n); + const xk = new Uint32Array(len + 28); + xk.set(k32); + for (let i2 = Nk; i2 < xk.length; i2++) { + let t = xk[i2 - 1]; + if (i2 % Nk === 0) + t = subByte(rotr32_8(t)) ^ xPowers[i2 / Nk - 1]; + else if (Nk > 6 && i2 % Nk === 4) + t = subByte(t); + xk[i2] = xk[i2 - Nk] ^ t; + } + return xk; + } + function expandKeyDecLE(key) { + const encKey = expandKeyLE(key); + const xk = encKey.slice(); + const Nk = encKey.length; + const { sbox2 } = tableEncoding; + const { T0, T1, T2, T3 } = tableDecoding; + for (let i2 = 0; i2 < Nk; i2 += 4) { + for (let j = 0; j < 4; j++) + xk[i2 + j] = encKey[Nk - i2 - 4 + j]; + } + encKey.fill(0); + for (let i2 = 4; i2 < Nk - 4; i2++) { + const x = xk[i2]; + const w = applySbox(sbox2, x, x, x, x); + xk[i2] = T0[w & 255] ^ T1[w >>> 8 & 255] ^ T2[w >>> 16 & 255] ^ T3[w >>> 24]; + } + return xk; + } + function apply0123(T01, T23, s0, s1, s2, s3) { + return T01[s0 << 8 & 65280 | s1 >>> 8 & 255] ^ T23[s2 >>> 8 & 65280 | s3 >>> 24 & 255]; + } + function applySbox(sbox2, s0, s1, s2, s3) { + return sbox2[s0 & 255 | s1 & 65280] | sbox2[s2 >>> 16 & 255 | s3 >>> 16 & 65280] << 16; + } + function encrypt(xk, s0, s1, s2, s3) { + const { sbox2, T01, T23 } = tableEncoding; + let k = 0; + s0 ^= xk[k++], s1 ^= xk[k++], s2 ^= xk[k++], s3 ^= xk[k++]; + const rounds = xk.length / 4 - 2; + for (let i2 = 0; i2 < rounds; i2++) { + const t02 = xk[k++] ^ apply0123(T01, T23, s0, s1, s2, s3); + const t12 = xk[k++] ^ apply0123(T01, T23, s1, s2, s3, s0); + const t22 = xk[k++] ^ apply0123(T01, T23, s2, s3, s0, s1); + const t32 = xk[k++] ^ apply0123(T01, T23, s3, s0, s1, s2); + s0 = t02, s1 = t12, s2 = t22, s3 = t32; + } + const t0 = xk[k++] ^ applySbox(sbox2, s0, s1, s2, s3); + const t1 = xk[k++] ^ applySbox(sbox2, s1, s2, s3, s0); + const t2 = xk[k++] ^ applySbox(sbox2, s2, s3, s0, s1); + const t3 = xk[k++] ^ applySbox(sbox2, s3, s0, s1, s2); + return { s0: t0, s1: t1, s2: t2, s3: t3 }; + } + function decrypt(xk, s0, s1, s2, s3) { + const { sbox2, T01, T23 } = tableDecoding; + let k = 0; + s0 ^= xk[k++], s1 ^= xk[k++], s2 ^= xk[k++], s3 ^= xk[k++]; + const rounds = xk.length / 4 - 2; + for (let i2 = 0; i2 < rounds; i2++) { + const t02 = xk[k++] ^ apply0123(T01, T23, s0, s3, s2, s1); + const t12 = xk[k++] ^ apply0123(T01, T23, s1, s0, s3, s2); + const t22 = xk[k++] ^ apply0123(T01, T23, s2, s1, s0, s3); + const t32 = xk[k++] ^ apply0123(T01, T23, s3, s2, s1, s0); + s0 = t02, s1 = t12, s2 = t22, s3 = t32; + } + const t0 = xk[k++] ^ applySbox(sbox2, s0, s3, s2, s1); + const t1 = xk[k++] ^ applySbox(sbox2, s1, s0, s3, s2); + const t2 = xk[k++] ^ applySbox(sbox2, s2, s1, s0, s3); + const t3 = xk[k++] ^ applySbox(sbox2, s3, s2, s1, s0); + return { s0: t0, s1: t1, s2: t2, s3: t3 }; + } + function getDst(len, dst) { + if (!dst) + return new Uint8Array(len); + bytes3(dst); + if (dst.length < len) + throw new Error(`aes: wrong destination length, expected at least ${len}, got: ${dst.length}`); + return dst; + } + function ctrCounter(xk, nonce, src, dst) { + bytes3(nonce, BLOCK_SIZE2); + bytes3(src); + const srcLen = src.length; + dst = getDst(srcLen, dst); + const ctr3 = nonce; + const c32 = u32(ctr3); + let { s0, s1, s2, s3 } = encrypt(xk, c32[0], c32[1], c32[2], c32[3]); + const src32 = u32(src); + const dst32 = u32(dst); + for (let i2 = 0; i2 + 4 <= src32.length; i2 += 4) { + dst32[i2 + 0] = src32[i2 + 0] ^ s0; + dst32[i2 + 1] = src32[i2 + 1] ^ s1; + dst32[i2 + 2] = src32[i2 + 2] ^ s2; + dst32[i2 + 3] = src32[i2 + 3] ^ s3; + let carry = 1; + for (let i3 = ctr3.length - 1; i3 >= 0; i3--) { + carry = carry + (ctr3[i3] & 255) | 0; + ctr3[i3] = carry & 255; + carry >>>= 8; + } + ({ s0, s1, s2, s3 } = encrypt(xk, c32[0], c32[1], c32[2], c32[3])); + } + const start = BLOCK_SIZE2 * Math.floor(src32.length / BLOCK_SIZE32); + if (start < srcLen) { + const b32 = new Uint32Array([s0, s1, s2, s3]); + const buf = u8(b32); + for (let i2 = start, pos = 0; i2 < srcLen; i2++, pos++) + dst[i2] = src[i2] ^ buf[pos]; + } + return dst; + } + function ctr32(xk, isLE4, nonce, src, dst) { + bytes3(nonce, BLOCK_SIZE2); + bytes3(src); + dst = getDst(src.length, dst); + const ctr3 = nonce; + const c32 = u32(ctr3); + const view = createView3(ctr3); + const src32 = u32(src); + const dst32 = u32(dst); + const ctrPos = isLE4 ? 0 : 12; + const srcLen = src.length; + let ctrNum = view.getUint32(ctrPos, isLE4); + let { s0, s1, s2, s3 } = encrypt(xk, c32[0], c32[1], c32[2], c32[3]); + for (let i2 = 0; i2 + 4 <= src32.length; i2 += 4) { + dst32[i2 + 0] = src32[i2 + 0] ^ s0; + dst32[i2 + 1] = src32[i2 + 1] ^ s1; + dst32[i2 + 2] = src32[i2 + 2] ^ s2; + dst32[i2 + 3] = src32[i2 + 3] ^ s3; + ctrNum = ctrNum + 1 >>> 0; + view.setUint32(ctrPos, ctrNum, isLE4); + ({ s0, s1, s2, s3 } = encrypt(xk, c32[0], c32[1], c32[2], c32[3])); + } + const start = BLOCK_SIZE2 * Math.floor(src32.length / BLOCK_SIZE32); + if (start < srcLen) { + const b32 = new Uint32Array([s0, s1, s2, s3]); + const buf = u8(b32); + for (let i2 = start, pos = 0; i2 < srcLen; i2++, pos++) + dst[i2] = src[i2] ^ buf[pos]; + } + return dst; + } + var ctr = wrapCipher({ blockSize: 16, nonceLength: 16 }, function ctr2(key, nonce) { + bytes3(key); + bytes3(nonce, BLOCK_SIZE2); + function processCtr(buf, dst) { + const xk = expandKeyLE(key); + const n = nonce.slice(); + const out = ctrCounter(xk, n, buf, dst); + xk.fill(0); + n.fill(0); + return out; + } + return { + encrypt: (plaintext, dst) => processCtr(plaintext, dst), + decrypt: (ciphertext, dst) => processCtr(ciphertext, dst) + }; + }); + function validateBlockDecrypt(data) { + bytes3(data); + if (data.length % BLOCK_SIZE2 !== 0) { + throw new Error(`aes/(cbc-ecb).decrypt ciphertext should consist of blocks with size ${BLOCK_SIZE2}`); + } + } + function validateBlockEncrypt(plaintext, pcks5, dst) { + let outLen = plaintext.length; + const remaining = outLen % BLOCK_SIZE2; + if (!pcks5 && remaining !== 0) + throw new Error("aec/(cbc-ecb): unpadded plaintext with disabled padding"); + const b = u32(plaintext); + if (pcks5) { + let left = BLOCK_SIZE2 - remaining; + if (!left) + left = BLOCK_SIZE2; + outLen = outLen + left; + } + const out = getDst(outLen, dst); + const o = u32(out); + return { b, o, out }; + } + function validatePCKS(data, pcks5) { + if (!pcks5) + return data; + const len = data.length; + if (!len) + throw new Error(`aes/pcks5: empty ciphertext not allowed`); + const lastByte = data[len - 1]; + if (lastByte <= 0 || lastByte > 16) + throw new Error(`aes/pcks5: wrong padding byte: ${lastByte}`); + const out = data.subarray(0, -lastByte); + for (let i2 = 0; i2 < lastByte; i2++) + if (data[len - i2 - 1] !== lastByte) + throw new Error(`aes/pcks5: wrong padding`); + return out; + } + function padPCKS(left) { + const tmp = new Uint8Array(16); + const tmp32 = u32(tmp); + tmp.set(left); + const paddingByte = BLOCK_SIZE2 - left.length; + for (let i2 = BLOCK_SIZE2 - paddingByte; i2 < BLOCK_SIZE2; i2++) + tmp[i2] = paddingByte; + return tmp32; + } + var ecb = wrapCipher({ blockSize: 16 }, function ecb2(key, opts = {}) { + bytes3(key); + const pcks5 = !opts.disablePadding; + return { + encrypt: (plaintext, dst) => { + bytes3(plaintext); + const { b, o, out: _out } = validateBlockEncrypt(plaintext, pcks5, dst); + const xk = expandKeyLE(key); + let i2 = 0; + for (; i2 + 4 <= b.length; ) { + const { s0, s1, s2, s3 } = encrypt(xk, b[i2 + 0], b[i2 + 1], b[i2 + 2], b[i2 + 3]); + o[i2++] = s0, o[i2++] = s1, o[i2++] = s2, o[i2++] = s3; + } + if (pcks5) { + const tmp32 = padPCKS(plaintext.subarray(i2 * 4)); + const { s0, s1, s2, s3 } = encrypt(xk, tmp32[0], tmp32[1], tmp32[2], tmp32[3]); + o[i2++] = s0, o[i2++] = s1, o[i2++] = s2, o[i2++] = s3; + } + xk.fill(0); + return _out; + }, + decrypt: (ciphertext, dst) => { + validateBlockDecrypt(ciphertext); + const xk = expandKeyDecLE(key); + const out = getDst(ciphertext.length, dst); + const b = u32(ciphertext); + const o = u32(out); + for (let i2 = 0; i2 + 4 <= b.length; ) { + const { s0, s1, s2, s3 } = decrypt(xk, b[i2 + 0], b[i2 + 1], b[i2 + 2], b[i2 + 3]); + o[i2++] = s0, o[i2++] = s1, o[i2++] = s2, o[i2++] = s3; + } + xk.fill(0); + return validatePCKS(out, pcks5); + } + }; + }); + var cbc = wrapCipher({ blockSize: 16, nonceLength: 16 }, function cbc2(key, iv, opts = {}) { + bytes3(key); + bytes3(iv, 16); + const pcks5 = !opts.disablePadding; + return { + encrypt: (plaintext, dst) => { + const xk = expandKeyLE(key); + const { b, o, out: _out } = validateBlockEncrypt(plaintext, pcks5, dst); + const n32 = u32(iv); + let s0 = n32[0], s1 = n32[1], s2 = n32[2], s3 = n32[3]; + let i2 = 0; + for (; i2 + 4 <= b.length; ) { + s0 ^= b[i2 + 0], s1 ^= b[i2 + 1], s2 ^= b[i2 + 2], s3 ^= b[i2 + 3]; + ({ s0, s1, s2, s3 } = encrypt(xk, s0, s1, s2, s3)); + o[i2++] = s0, o[i2++] = s1, o[i2++] = s2, o[i2++] = s3; + } + if (pcks5) { + const tmp32 = padPCKS(plaintext.subarray(i2 * 4)); + s0 ^= tmp32[0], s1 ^= tmp32[1], s2 ^= tmp32[2], s3 ^= tmp32[3]; + ({ s0, s1, s2, s3 } = encrypt(xk, s0, s1, s2, s3)); + o[i2++] = s0, o[i2++] = s1, o[i2++] = s2, o[i2++] = s3; + } + xk.fill(0); + return _out; + }, + decrypt: (ciphertext, dst) => { + validateBlockDecrypt(ciphertext); + const xk = expandKeyDecLE(key); + const n32 = u32(iv); + const out = getDst(ciphertext.length, dst); + const b = u32(ciphertext); + const o = u32(out); + let s0 = n32[0], s1 = n32[1], s2 = n32[2], s3 = n32[3]; + for (let i2 = 0; i2 + 4 <= b.length; ) { + const ps0 = s0, ps1 = s1, ps2 = s2, ps3 = s3; + s0 = b[i2 + 0], s1 = b[i2 + 1], s2 = b[i2 + 2], s3 = b[i2 + 3]; + const { s0: o0, s1: o1, s2: o2, s3: o3 } = decrypt(xk, s0, s1, s2, s3); + o[i2++] = o0 ^ ps0, o[i2++] = o1 ^ ps1, o[i2++] = o2 ^ ps2, o[i2++] = o3 ^ ps3; + } + xk.fill(0); + return validatePCKS(out, pcks5); + } + }; + }); + function computeTag(fn, isLE4, key, data, AAD) { + const h = fn.create(key, data.length + (AAD?.length || 0)); + if (AAD) + h.update(AAD); + h.update(data); + const num = new Uint8Array(16); + const view = createView3(num); + if (AAD) + setBigUint643(view, 0, BigInt(AAD.length * 8), isLE4); + setBigUint643(view, 8, BigInt(data.length * 8), isLE4); + h.update(num); + return h.digest(); + } + var gcm = wrapCipher({ blockSize: 16, nonceLength: 12, tagLength: 16 }, function gcm2(key, nonce, AAD) { + bytes3(nonce); + if (nonce.length === 0) + throw new Error("aes/gcm: empty nonce"); + const tagLength = 16; + function _computeTag(authKey, tagMask, data) { + const tag = computeTag(ghash, false, authKey, data, AAD); + for (let i2 = 0; i2 < tagMask.length; i2++) + tag[i2] ^= tagMask[i2]; + return tag; + } + function deriveKeys() { + const xk = expandKeyLE(key); + const authKey = EMPTY_BLOCK.slice(); + const counter = EMPTY_BLOCK.slice(); + ctr32(xk, false, counter, counter, authKey); + if (nonce.length === 12) { + counter.set(nonce); + } else { + const nonceLen = EMPTY_BLOCK.slice(); + const view = createView3(nonceLen); + setBigUint643(view, 8, BigInt(nonce.length * 8), false); + ghash.create(authKey).update(nonce).update(nonceLen).digestInto(counter); + } + const tagMask = ctr32(xk, false, counter, EMPTY_BLOCK); + return { xk, authKey, counter, tagMask }; + } + return { + encrypt: (plaintext) => { + bytes3(plaintext); + const { xk, authKey, counter, tagMask } = deriveKeys(); + const out = new Uint8Array(plaintext.length + tagLength); + ctr32(xk, false, counter, plaintext, out); + const tag = _computeTag(authKey, tagMask, out.subarray(0, out.length - tagLength)); + out.set(tag, plaintext.length); + xk.fill(0); + return out; + }, + decrypt: (ciphertext) => { + bytes3(ciphertext); + if (ciphertext.length < tagLength) + throw new Error(`aes/gcm: ciphertext less than tagLen (${tagLength})`); + const { xk, authKey, counter, tagMask } = deriveKeys(); + const data = ciphertext.subarray(0, -tagLength); + const passedTag = ciphertext.subarray(-tagLength); + const tag = _computeTag(authKey, tagMask, data); + if (!equalBytes2(tag, passedTag)) + throw new Error("aes/gcm: invalid ghash tag"); + const out = ctr32(xk, false, counter, data); + authKey.fill(0); + tagMask.fill(0); + xk.fill(0); + return out; + } + }; + }); + var limit = (name, min, max) => (value) => { + if (!Number.isSafeInteger(value) || min > value || value > max) + throw new Error(`${name}: invalid value=${value}, must be [${min}..${max}]`); + }; + var siv = wrapCipher({ blockSize: 16, nonceLength: 12, tagLength: 16 }, function siv2(key, nonce, AAD) { + const tagLength = 16; + const AAD_LIMIT = limit("AAD", 0, 2 ** 36); + const PLAIN_LIMIT = limit("plaintext", 0, 2 ** 36); + const NONCE_LIMIT = limit("nonce", 12, 12); + const CIPHER_LIMIT = limit("ciphertext", 16, 2 ** 36 + 16); + bytes3(nonce); + NONCE_LIMIT(nonce.length); + if (AAD) { + bytes3(AAD); + AAD_LIMIT(AAD.length); + } + function deriveKeys() { + const len = key.length; + if (len !== 16 && len !== 24 && len !== 32) + throw new Error(`key length must be 16, 24 or 32 bytes, got: ${len} bytes`); + const xk = expandKeyLE(key); + const encKey = new Uint8Array(len); + const authKey = new Uint8Array(16); + const n32 = u32(nonce); + let s0 = 0, s1 = n32[0], s2 = n32[1], s3 = n32[2]; + let counter = 0; + for (const derivedKey of [authKey, encKey].map(u32)) { + const d32 = u32(derivedKey); + for (let i2 = 0; i2 < d32.length; i2 += 2) { + const { s0: o0, s1: o1 } = encrypt(xk, s0, s1, s2, s3); + d32[i2 + 0] = o0; + d32[i2 + 1] = o1; + s0 = ++counter; + } + } + xk.fill(0); + return { authKey, encKey: expandKeyLE(encKey) }; + } + function _computeTag(encKey, authKey, data) { + const tag = computeTag(polyval, true, authKey, data, AAD); + for (let i2 = 0; i2 < 12; i2++) + tag[i2] ^= nonce[i2]; + tag[15] &= 127; + const t32 = u32(tag); + let s0 = t32[0], s1 = t32[1], s2 = t32[2], s3 = t32[3]; + ({ s0, s1, s2, s3 } = encrypt(encKey, s0, s1, s2, s3)); + t32[0] = s0, t32[1] = s1, t32[2] = s2, t32[3] = s3; + return tag; + } + function processSiv(encKey, tag, input) { + let block = tag.slice(); + block[15] |= 128; + return ctr32(encKey, true, block, input); + } + return { + encrypt: (plaintext) => { + bytes3(plaintext); + PLAIN_LIMIT(plaintext.length); + const { encKey, authKey } = deriveKeys(); + const tag = _computeTag(encKey, authKey, plaintext); + const out = new Uint8Array(plaintext.length + tagLength); + out.set(tag, plaintext.length); + out.set(processSiv(encKey, tag, plaintext)); + encKey.fill(0); + authKey.fill(0); + return out; + }, + decrypt: (ciphertext) => { + bytes3(ciphertext); + CIPHER_LIMIT(ciphertext.length); + const tag = ciphertext.subarray(-tagLength); + const { encKey, authKey } = deriveKeys(); + const plaintext = processSiv(encKey, tag, ciphertext.subarray(0, -tagLength)); + const expectedTag = _computeTag(encKey, authKey, plaintext); + encKey.fill(0); + authKey.fill(0); + if (!equalBytes2(tag, expectedTag)) + throw new Error("invalid polyval tag"); + return plaintext; + } + }; + }); + + // nip04.ts + function encrypt2(secretKey, pubkey, text) { + const privkey = secretKey instanceof Uint8Array ? bytesToHex2(secretKey) : secretKey; + const key = secp256k1.getSharedSecret(privkey, "02" + pubkey); + const normalizedKey = getNormalizedX(key); + let iv = Uint8Array.from(randomBytes2(16)); + let plaintext = utf8Encoder.encode(text); + let ciphertext = cbc(normalizedKey, iv).encrypt(plaintext); + let ctb64 = base64.encode(new Uint8Array(ciphertext)); + let ivb64 = base64.encode(new Uint8Array(iv.buffer)); + return `${ctb64}?iv=${ivb64}`; + } + function decrypt2(secretKey, pubkey, data) { + const privkey = secretKey instanceof Uint8Array ? bytesToHex2(secretKey) : secretKey; + let [ctb64, ivb64] = data.split("?iv="); + let key = secp256k1.getSharedSecret(privkey, "02" + pubkey); + let normalizedKey = getNormalizedX(key); + let iv = base64.decode(ivb64); + let ciphertext = base64.decode(ctb64); + let plaintext = cbc(normalizedKey, iv).decrypt(ciphertext); + return utf8Decoder.decode(plaintext); + } + function getNormalizedX(key) { + return key.slice(1, 33); + } + + // nip05.ts + var nip05_exports = {}; + __export(nip05_exports, { + NIP05_REGEX: () => NIP05_REGEX, + isNip05: () => isNip05, + isValid: () => isValid, + queryProfile: () => queryProfile, + searchDomain: () => searchDomain, + useFetchImplementation: () => useFetchImplementation + }); + var NIP05_REGEX = /^(?:([\w.+-]+)@)?([\w_-]+(\.[\w_-]+)+)$/; + var isNip05 = (value) => NIP05_REGEX.test(value || ""); + var _fetch; + try { + _fetch = fetch; + } catch (_) { + null; + } + function useFetchImplementation(fetchImplementation) { + _fetch = fetchImplementation; + } + async function searchDomain(domain, query = "") { + try { + const url = `https://${domain}/.well-known/nostr.json?name=${query}`; + const res = await _fetch(url, { redirect: "manual" }); + if (res.status !== 200) { + throw Error("Wrong response code"); + } + const json = await res.json(); + return json.names; + } catch (_) { + return {}; + } + } + async function queryProfile(fullname) { + const match = fullname.match(NIP05_REGEX); + if (!match) + return null; + const [, name = "_", domain] = match; + try { + const url = `https://${domain}/.well-known/nostr.json?name=${name}`; + const res = await _fetch(url, { redirect: "manual" }); + if (res.status !== 200) { + throw Error("Wrong response code"); + } + const json = await res.json(); + const pubkey = json.names[name]; + return pubkey ? { pubkey, relays: json.relays?.[pubkey] } : null; + } catch (_e) { + return null; + } + } + async function isValid(pubkey, nip05) { + const res = await queryProfile(nip05); + return res ? res.pubkey === pubkey : false; + } + + // nip10.ts + var nip10_exports = {}; + __export(nip10_exports, { + parse: () => parse + }); + function parse(event) { + const result = { + reply: void 0, + root: void 0, + mentions: [], + profiles: [], + quotes: [] + }; + let maybeParent; + let maybeRoot; + for (let i2 = event.tags.length - 1; i2 >= 0; i2--) { + const tag = event.tags[i2]; + if (tag[0] === "e" && tag[1]) { + const [_, eTagEventId, eTagRelayUrl, eTagMarker, eTagAuthor] = tag; + const eventPointer = { + id: eTagEventId, + relays: eTagRelayUrl ? [eTagRelayUrl] : [], + author: eTagAuthor + }; + if (eTagMarker === "root") { + result.root = eventPointer; + continue; + } + if (eTagMarker === "reply") { + result.reply = eventPointer; + continue; + } + if (eTagMarker === "mention") { + result.mentions.push(eventPointer); + continue; + } + if (!maybeParent) { + maybeParent = eventPointer; + } else { + maybeRoot = eventPointer; + } + result.mentions.push(eventPointer); + continue; + } + if (tag[0] === "q" && tag[1]) { + const [_, eTagEventId, eTagRelayUrl] = tag; + result.quotes.push({ + id: eTagEventId, + relays: eTagRelayUrl ? [eTagRelayUrl] : [] + }); + } + if (tag[0] === "p" && tag[1]) { + result.profiles.push({ + pubkey: tag[1], + relays: tag[2] ? [tag[2]] : [] + }); + continue; + } + } + if (!result.root) { + result.root = maybeRoot || maybeParent || result.reply; + } + if (!result.reply) { + result.reply = maybeParent || result.root; + } + ; + [result.reply, result.root].forEach((ref) => { + if (!ref) + return; + let idx = result.mentions.indexOf(ref); + if (idx !== -1) { + result.mentions.splice(idx, 1); + } + if (ref.author) { + let author = result.profiles.find((p) => p.pubkey === ref.author); + if (author && author.relays) { + if (!ref.relays) { + ref.relays = []; + } + author.relays.forEach((url) => { + if (ref.relays?.indexOf(url) === -1) + ref.relays.push(url); + }); + author.relays = ref.relays; + } + } + }); + result.mentions.forEach((ref) => { + if (ref.author) { + let author = result.profiles.find((p) => p.pubkey === ref.author); + if (author && author.relays) { + if (!ref.relays) { + ref.relays = []; + } + author.relays.forEach((url) => { + if (ref.relays.indexOf(url) === -1) + ref.relays.push(url); + }); + author.relays = ref.relays; + } + } + }); + return result; + } + + // nip11.ts + var nip11_exports = {}; + __export(nip11_exports, { + fetchRelayInformation: () => fetchRelayInformation, + useFetchImplementation: () => useFetchImplementation2 + }); + var _fetch2; + try { + _fetch2 = fetch; + } catch { + } + function useFetchImplementation2(fetchImplementation) { + _fetch2 = fetchImplementation; + } + async function fetchRelayInformation(url) { + return await (await fetch(url.replace("ws://", "http://").replace("wss://", "https://"), { + headers: { Accept: "application/nostr+json" } + })).json(); + } + + // nip13.ts + var nip13_exports = {}; + __export(nip13_exports, { + fastEventHash: () => fastEventHash, + getPow: () => getPow, + minePow: () => minePow + }); + function getPow(hex2) { + let count = 0; + for (let i2 = 0; i2 < 64; i2 += 8) { + const nibble = parseInt(hex2.substring(i2, i2 + 8), 16); + if (nibble === 0) { + count += 32; + } else { + count += Math.clz32(nibble); + break; + } + } + return count; + } + function minePow(unsigned, difficulty) { + let count = 0; + const event = unsigned; + const tag = ["nonce", count.toString(), difficulty.toString()]; + event.tags.push(tag); + while (true) { + const now2 = Math.floor(new Date().getTime() / 1e3); + if (now2 !== event.created_at) { + count = 0; + event.created_at = now2; + } + tag[1] = (++count).toString(); + event.id = fastEventHash(event); + if (getPow(event.id) >= difficulty) { + break; + } + } + return event; + } + function fastEventHash(evt) { + return bytesToHex2( + sha2562(utf8Encoder.encode(JSON.stringify([0, evt.pubkey, evt.created_at, evt.kind, evt.tags, evt.content]))) + ); + } + + // nip17.ts + var nip17_exports = {}; + __export(nip17_exports, { + unwrapEvent: () => unwrapEvent2, + unwrapManyEvents: () => unwrapManyEvents2, + wrapEvent: () => wrapEvent2, + wrapManyEvents: () => wrapManyEvents2 + }); + + // nip59.ts + var nip59_exports = {}; + __export(nip59_exports, { + createRumor: () => createRumor, + createSeal: () => createSeal, + createWrap: () => createWrap, + unwrapEvent: () => unwrapEvent, + unwrapManyEvents: () => unwrapManyEvents, + wrapEvent: () => wrapEvent, + wrapManyEvents: () => wrapManyEvents + }); + + // nip44.ts + var nip44_exports = {}; + __export(nip44_exports, { + decrypt: () => decrypt3, + encrypt: () => encrypt3, + getConversationKey: () => getConversationKey, + v2: () => v2 + }); + + // node_modules/@noble/ciphers/esm/_poly1305.js + var u8to16 = (a, i2) => a[i2++] & 255 | (a[i2++] & 255) << 8; + var Poly1305 = class { + constructor(key) { + this.blockLen = 16; + this.outputLen = 16; + this.buffer = new Uint8Array(16); + this.r = new Uint16Array(10); + this.h = new Uint16Array(10); + this.pad = new Uint16Array(8); + this.pos = 0; + this.finished = false; + key = toBytes3(key); + bytes3(key, 32); + const t0 = u8to16(key, 0); + const t1 = u8to16(key, 2); + const t2 = u8to16(key, 4); + const t3 = u8to16(key, 6); + const t4 = u8to16(key, 8); + const t5 = u8to16(key, 10); + const t6 = u8to16(key, 12); + const t7 = u8to16(key, 14); + this.r[0] = t0 & 8191; + this.r[1] = (t0 >>> 13 | t1 << 3) & 8191; + this.r[2] = (t1 >>> 10 | t2 << 6) & 7939; + this.r[3] = (t2 >>> 7 | t3 << 9) & 8191; + this.r[4] = (t3 >>> 4 | t4 << 12) & 255; + this.r[5] = t4 >>> 1 & 8190; + this.r[6] = (t4 >>> 14 | t5 << 2) & 8191; + this.r[7] = (t5 >>> 11 | t6 << 5) & 8065; + this.r[8] = (t6 >>> 8 | t7 << 8) & 8191; + this.r[9] = t7 >>> 5 & 127; + for (let i2 = 0; i2 < 8; i2++) + this.pad[i2] = u8to16(key, 16 + 2 * i2); + } + process(data, offset, isLast = false) { + const hibit = isLast ? 0 : 1 << 11; + const { h, r } = this; + const r0 = r[0]; + const r1 = r[1]; + const r2 = r[2]; + const r3 = r[3]; + const r4 = r[4]; + const r5 = r[5]; + const r6 = r[6]; + const r7 = r[7]; + const r8 = r[8]; + const r9 = r[9]; + const t0 = u8to16(data, offset + 0); + const t1 = u8to16(data, offset + 2); + const t2 = u8to16(data, offset + 4); + const t3 = u8to16(data, offset + 6); + const t4 = u8to16(data, offset + 8); + const t5 = u8to16(data, offset + 10); + const t6 = u8to16(data, offset + 12); + const t7 = u8to16(data, offset + 14); + let h0 = h[0] + (t0 & 8191); + let h1 = h[1] + ((t0 >>> 13 | t1 << 3) & 8191); + let h2 = h[2] + ((t1 >>> 10 | t2 << 6) & 8191); + let h3 = h[3] + ((t2 >>> 7 | t3 << 9) & 8191); + let h4 = h[4] + ((t3 >>> 4 | t4 << 12) & 8191); + let h5 = h[5] + (t4 >>> 1 & 8191); + let h6 = h[6] + ((t4 >>> 14 | t5 << 2) & 8191); + let h7 = h[7] + ((t5 >>> 11 | t6 << 5) & 8191); + let h8 = h[8] + ((t6 >>> 8 | t7 << 8) & 8191); + let h9 = h[9] + (t7 >>> 5 | hibit); + let c = 0; + let d0 = c + h0 * r0 + h1 * (5 * r9) + h2 * (5 * r8) + h3 * (5 * r7) + h4 * (5 * r6); + c = d0 >>> 13; + d0 &= 8191; + d0 += h5 * (5 * r5) + h6 * (5 * r4) + h7 * (5 * r3) + h8 * (5 * r2) + h9 * (5 * r1); + c += d0 >>> 13; + d0 &= 8191; + let d1 = c + h0 * r1 + h1 * r0 + h2 * (5 * r9) + h3 * (5 * r8) + h4 * (5 * r7); + c = d1 >>> 13; + d1 &= 8191; + d1 += h5 * (5 * r6) + h6 * (5 * r5) + h7 * (5 * r4) + h8 * (5 * r3) + h9 * (5 * r2); + c += d1 >>> 13; + d1 &= 8191; + let d2 = c + h0 * r2 + h1 * r1 + h2 * r0 + h3 * (5 * r9) + h4 * (5 * r8); + c = d2 >>> 13; + d2 &= 8191; + d2 += h5 * (5 * r7) + h6 * (5 * r6) + h7 * (5 * r5) + h8 * (5 * r4) + h9 * (5 * r3); + c += d2 >>> 13; + d2 &= 8191; + let d3 = c + h0 * r3 + h1 * r2 + h2 * r1 + h3 * r0 + h4 * (5 * r9); + c = d3 >>> 13; + d3 &= 8191; + d3 += h5 * (5 * r8) + h6 * (5 * r7) + h7 * (5 * r6) + h8 * (5 * r5) + h9 * (5 * r4); + c += d3 >>> 13; + d3 &= 8191; + let d4 = c + h0 * r4 + h1 * r3 + h2 * r2 + h3 * r1 + h4 * r0; + c = d4 >>> 13; + d4 &= 8191; + d4 += h5 * (5 * r9) + h6 * (5 * r8) + h7 * (5 * r7) + h8 * (5 * r6) + h9 * (5 * r5); + c += d4 >>> 13; + d4 &= 8191; + let d5 = c + h0 * r5 + h1 * r4 + h2 * r3 + h3 * r2 + h4 * r1; + c = d5 >>> 13; + d5 &= 8191; + d5 += h5 * r0 + h6 * (5 * r9) + h7 * (5 * r8) + h8 * (5 * r7) + h9 * (5 * r6); + c += d5 >>> 13; + d5 &= 8191; + let d6 = c + h0 * r6 + h1 * r5 + h2 * r4 + h3 * r3 + h4 * r2; + c = d6 >>> 13; + d6 &= 8191; + d6 += h5 * r1 + h6 * r0 + h7 * (5 * r9) + h8 * (5 * r8) + h9 * (5 * r7); + c += d6 >>> 13; + d6 &= 8191; + let d7 = c + h0 * r7 + h1 * r6 + h2 * r5 + h3 * r4 + h4 * r3; + c = d7 >>> 13; + d7 &= 8191; + d7 += h5 * r2 + h6 * r1 + h7 * r0 + h8 * (5 * r9) + h9 * (5 * r8); + c += d7 >>> 13; + d7 &= 8191; + let d8 = c + h0 * r8 + h1 * r7 + h2 * r6 + h3 * r5 + h4 * r4; + c = d8 >>> 13; + d8 &= 8191; + d8 += h5 * r3 + h6 * r2 + h7 * r1 + h8 * r0 + h9 * (5 * r9); + c += d8 >>> 13; + d8 &= 8191; + let d9 = c + h0 * r9 + h1 * r8 + h2 * r7 + h3 * r6 + h4 * r5; + c = d9 >>> 13; + d9 &= 8191; + d9 += h5 * r4 + h6 * r3 + h7 * r2 + h8 * r1 + h9 * r0; + c += d9 >>> 13; + d9 &= 8191; + c = (c << 2) + c | 0; + c = c + d0 | 0; + d0 = c & 8191; + c = c >>> 13; + d1 += c; + h[0] = d0; + h[1] = d1; + h[2] = d2; + h[3] = d3; + h[4] = d4; + h[5] = d5; + h[6] = d6; + h[7] = d7; + h[8] = d8; + h[9] = d9; + } + finalize() { + const { h, pad: pad2 } = this; + const g = new Uint16Array(10); + let c = h[1] >>> 13; + h[1] &= 8191; + for (let i2 = 2; i2 < 10; i2++) { + h[i2] += c; + c = h[i2] >>> 13; + h[i2] &= 8191; + } + h[0] += c * 5; + c = h[0] >>> 13; + h[0] &= 8191; + h[1] += c; + c = h[1] >>> 13; + h[1] &= 8191; + h[2] += c; + g[0] = h[0] + 5; + c = g[0] >>> 13; + g[0] &= 8191; + for (let i2 = 1; i2 < 10; i2++) { + g[i2] = h[i2] + c; + c = g[i2] >>> 13; + g[i2] &= 8191; + } + g[9] -= 1 << 13; + let mask = (c ^ 1) - 1; + for (let i2 = 0; i2 < 10; i2++) + g[i2] &= mask; + mask = ~mask; + for (let i2 = 0; i2 < 10; i2++) + h[i2] = h[i2] & mask | g[i2]; + h[0] = (h[0] | h[1] << 13) & 65535; + h[1] = (h[1] >>> 3 | h[2] << 10) & 65535; + h[2] = (h[2] >>> 6 | h[3] << 7) & 65535; + h[3] = (h[3] >>> 9 | h[4] << 4) & 65535; + h[4] = (h[4] >>> 12 | h[5] << 1 | h[6] << 14) & 65535; + h[5] = (h[6] >>> 2 | h[7] << 11) & 65535; + h[6] = (h[7] >>> 5 | h[8] << 8) & 65535; + h[7] = (h[8] >>> 8 | h[9] << 5) & 65535; + let f = h[0] + pad2[0]; + h[0] = f & 65535; + for (let i2 = 1; i2 < 8; i2++) { + f = (h[i2] + pad2[i2] | 0) + (f >>> 16) | 0; + h[i2] = f & 65535; + } + } + update(data) { + exists3(this); + const { buffer, blockLen } = this; + data = toBytes3(data); + const len = data.length; + for (let pos = 0; pos < len; ) { + const take = Math.min(blockLen - this.pos, len - pos); + if (take === blockLen) { + for (; blockLen <= len - pos; pos += blockLen) + this.process(data, pos); + continue; + } + buffer.set(data.subarray(pos, pos + take), this.pos); + this.pos += take; + pos += take; + if (this.pos === blockLen) { + this.process(buffer, 0, false); + this.pos = 0; + } + } + return this; + } + destroy() { + this.h.fill(0); + this.r.fill(0); + this.buffer.fill(0); + this.pad.fill(0); + } + digestInto(out) { + exists3(this); + output3(out, this); + this.finished = true; + const { buffer, h } = this; + let { pos } = this; + if (pos) { + buffer[pos++] = 1; + for (; pos < 16; pos++) + buffer[pos] = 0; + this.process(buffer, 0, true); + } + this.finalize(); + let opos = 0; + for (let i2 = 0; i2 < 8; i2++) { + out[opos++] = h[i2] >>> 0; + out[opos++] = h[i2] >>> 8; + } + return out; + } + digest() { + const { buffer, outputLen } = this; + this.digestInto(buffer); + const res = buffer.slice(0, outputLen); + this.destroy(); + return res; + } + }; + function wrapConstructorWithKey2(hashCons) { + const hashC = (msg, key) => hashCons(key).update(toBytes3(msg)).digest(); + const tmp = hashCons(new Uint8Array(32)); + hashC.outputLen = tmp.outputLen; + hashC.blockLen = tmp.blockLen; + hashC.create = (key) => hashCons(key); + return hashC; + } + var poly1305 = wrapConstructorWithKey2((key) => new Poly1305(key)); + + // node_modules/@noble/ciphers/esm/_arx.js + var sigma16 = utf8ToBytes4("expand 16-byte k"); + var sigma32 = utf8ToBytes4("expand 32-byte k"); + var sigma16_32 = u32(sigma16); + var sigma32_32 = u32(sigma32); + function rotl(a, b) { + return a << b | a >>> 32 - b; + } + function isAligned32(b) { + return b.byteOffset % 4 === 0; + } + var BLOCK_LEN = 64; + var BLOCK_LEN32 = 16; + var MAX_COUNTER = 2 ** 32 - 1; + var U32_EMPTY = new Uint32Array(); + function runCipher(core, sigma, key, nonce, data, output4, counter, rounds) { + const len = data.length; + const block = new Uint8Array(BLOCK_LEN); + const b32 = u32(block); + const isAligned = isAligned32(data) && isAligned32(output4); + const d32 = isAligned ? u32(data) : U32_EMPTY; + const o32 = isAligned ? u32(output4) : U32_EMPTY; + for (let pos = 0; pos < len; counter++) { + core(sigma, key, nonce, b32, counter, rounds); + if (counter >= MAX_COUNTER) + throw new Error("arx: counter overflow"); + const take = Math.min(BLOCK_LEN, len - pos); + if (isAligned && take === BLOCK_LEN) { + const pos32 = pos / 4; + if (pos % 4 !== 0) + throw new Error("arx: invalid block position"); + for (let j = 0, posj; j < BLOCK_LEN32; j++) { + posj = pos32 + j; + o32[posj] = d32[posj] ^ b32[j]; + } + pos += BLOCK_LEN; + continue; + } + for (let j = 0, posj; j < take; j++) { + posj = pos + j; + output4[posj] = data[posj] ^ block[j]; + } + pos += take; + } + } + function createCipher(core, opts) { + const { allowShortKeys, extendNonceFn, counterLength, counterRight, rounds } = checkOpts({ allowShortKeys: false, counterLength: 8, counterRight: false, rounds: 20 }, opts); + if (typeof core !== "function") + throw new Error("core must be a function"); + number3(counterLength); + number3(rounds); + bool2(counterRight); + bool2(allowShortKeys); + return (key, nonce, data, output4, counter = 0) => { + bytes3(key); + bytes3(nonce); + bytes3(data); + const len = data.length; + if (!output4) + output4 = new Uint8Array(len); + bytes3(output4); + number3(counter); + if (counter < 0 || counter >= MAX_COUNTER) + throw new Error("arx: counter overflow"); + if (output4.length < len) + throw new Error(`arx: output (${output4.length}) is shorter than data (${len})`); + const toClean = []; + let l = key.length, k, sigma; + if (l === 32) { + k = key.slice(); + toClean.push(k); + sigma = sigma32_32; + } else if (l === 16 && allowShortKeys) { + k = new Uint8Array(32); + k.set(key); + k.set(key, 16); + sigma = sigma16_32; + toClean.push(k); + } else { + throw new Error(`arx: invalid 32-byte key, got length=${l}`); + } + if (!isAligned32(nonce)) { + nonce = nonce.slice(); + toClean.push(nonce); + } + const k32 = u32(k); + if (extendNonceFn) { + if (nonce.length !== 24) + throw new Error(`arx: extended nonce must be 24 bytes`); + extendNonceFn(sigma, k32, u32(nonce.subarray(0, 16)), k32); + nonce = nonce.subarray(16); + } + const nonceNcLen = 16 - counterLength; + if (nonceNcLen !== nonce.length) + throw new Error(`arx: nonce must be ${nonceNcLen} or 16 bytes`); + if (nonceNcLen !== 12) { + const nc = new Uint8Array(12); + nc.set(nonce, counterRight ? 0 : 12 - nonce.length); + nonce = nc; + toClean.push(nonce); + } + const n32 = u32(nonce); + runCipher(core, sigma, k32, n32, data, output4, counter, rounds); + while (toClean.length > 0) + toClean.pop().fill(0); + return output4; + }; + } + + // node_modules/@noble/ciphers/esm/chacha.js + function chachaCore(s, k, n, out, cnt, rounds = 20) { + let y00 = s[0], y01 = s[1], y02 = s[2], y03 = s[3], y04 = k[0], y05 = k[1], y06 = k[2], y07 = k[3], y08 = k[4], y09 = k[5], y10 = k[6], y11 = k[7], y12 = cnt, y13 = n[0], y14 = n[1], y15 = n[2]; + let x00 = y00, x01 = y01, x02 = y02, x03 = y03, x04 = y04, x05 = y05, x06 = y06, x07 = y07, x08 = y08, x09 = y09, x10 = y10, x11 = y11, x12 = y12, x13 = y13, x14 = y14, x15 = y15; + for (let r = 0; r < rounds; r += 2) { + x00 = x00 + x04 | 0; + x12 = rotl(x12 ^ x00, 16); + x08 = x08 + x12 | 0; + x04 = rotl(x04 ^ x08, 12); + x00 = x00 + x04 | 0; + x12 = rotl(x12 ^ x00, 8); + x08 = x08 + x12 | 0; + x04 = rotl(x04 ^ x08, 7); + x01 = x01 + x05 | 0; + x13 = rotl(x13 ^ x01, 16); + x09 = x09 + x13 | 0; + x05 = rotl(x05 ^ x09, 12); + x01 = x01 + x05 | 0; + x13 = rotl(x13 ^ x01, 8); + x09 = x09 + x13 | 0; + x05 = rotl(x05 ^ x09, 7); + x02 = x02 + x06 | 0; + x14 = rotl(x14 ^ x02, 16); + x10 = x10 + x14 | 0; + x06 = rotl(x06 ^ x10, 12); + x02 = x02 + x06 | 0; + x14 = rotl(x14 ^ x02, 8); + x10 = x10 + x14 | 0; + x06 = rotl(x06 ^ x10, 7); + x03 = x03 + x07 | 0; + x15 = rotl(x15 ^ x03, 16); + x11 = x11 + x15 | 0; + x07 = rotl(x07 ^ x11, 12); + x03 = x03 + x07 | 0; + x15 = rotl(x15 ^ x03, 8); + x11 = x11 + x15 | 0; + x07 = rotl(x07 ^ x11, 7); + x00 = x00 + x05 | 0; + x15 = rotl(x15 ^ x00, 16); + x10 = x10 + x15 | 0; + x05 = rotl(x05 ^ x10, 12); + x00 = x00 + x05 | 0; + x15 = rotl(x15 ^ x00, 8); + x10 = x10 + x15 | 0; + x05 = rotl(x05 ^ x10, 7); + x01 = x01 + x06 | 0; + x12 = rotl(x12 ^ x01, 16); + x11 = x11 + x12 | 0; + x06 = rotl(x06 ^ x11, 12); + x01 = x01 + x06 | 0; + x12 = rotl(x12 ^ x01, 8); + x11 = x11 + x12 | 0; + x06 = rotl(x06 ^ x11, 7); + x02 = x02 + x07 | 0; + x13 = rotl(x13 ^ x02, 16); + x08 = x08 + x13 | 0; + x07 = rotl(x07 ^ x08, 12); + x02 = x02 + x07 | 0; + x13 = rotl(x13 ^ x02, 8); + x08 = x08 + x13 | 0; + x07 = rotl(x07 ^ x08, 7); + x03 = x03 + x04 | 0; + x14 = rotl(x14 ^ x03, 16); + x09 = x09 + x14 | 0; + x04 = rotl(x04 ^ x09, 12); + x03 = x03 + x04 | 0; + x14 = rotl(x14 ^ x03, 8); + x09 = x09 + x14 | 0; + x04 = rotl(x04 ^ x09, 7); + } + let oi = 0; + out[oi++] = y00 + x00 | 0; + out[oi++] = y01 + x01 | 0; + out[oi++] = y02 + x02 | 0; + out[oi++] = y03 + x03 | 0; + out[oi++] = y04 + x04 | 0; + out[oi++] = y05 + x05 | 0; + out[oi++] = y06 + x06 | 0; + out[oi++] = y07 + x07 | 0; + out[oi++] = y08 + x08 | 0; + out[oi++] = y09 + x09 | 0; + out[oi++] = y10 + x10 | 0; + out[oi++] = y11 + x11 | 0; + out[oi++] = y12 + x12 | 0; + out[oi++] = y13 + x13 | 0; + out[oi++] = y14 + x14 | 0; + out[oi++] = y15 + x15 | 0; + } + function hchacha(s, k, i2, o32) { + let x00 = s[0], x01 = s[1], x02 = s[2], x03 = s[3], x04 = k[0], x05 = k[1], x06 = k[2], x07 = k[3], x08 = k[4], x09 = k[5], x10 = k[6], x11 = k[7], x12 = i2[0], x13 = i2[1], x14 = i2[2], x15 = i2[3]; + for (let r = 0; r < 20; r += 2) { + x00 = x00 + x04 | 0; + x12 = rotl(x12 ^ x00, 16); + x08 = x08 + x12 | 0; + x04 = rotl(x04 ^ x08, 12); + x00 = x00 + x04 | 0; + x12 = rotl(x12 ^ x00, 8); + x08 = x08 + x12 | 0; + x04 = rotl(x04 ^ x08, 7); + x01 = x01 + x05 | 0; + x13 = rotl(x13 ^ x01, 16); + x09 = x09 + x13 | 0; + x05 = rotl(x05 ^ x09, 12); + x01 = x01 + x05 | 0; + x13 = rotl(x13 ^ x01, 8); + x09 = x09 + x13 | 0; + x05 = rotl(x05 ^ x09, 7); + x02 = x02 + x06 | 0; + x14 = rotl(x14 ^ x02, 16); + x10 = x10 + x14 | 0; + x06 = rotl(x06 ^ x10, 12); + x02 = x02 + x06 | 0; + x14 = rotl(x14 ^ x02, 8); + x10 = x10 + x14 | 0; + x06 = rotl(x06 ^ x10, 7); + x03 = x03 + x07 | 0; + x15 = rotl(x15 ^ x03, 16); + x11 = x11 + x15 | 0; + x07 = rotl(x07 ^ x11, 12); + x03 = x03 + x07 | 0; + x15 = rotl(x15 ^ x03, 8); + x11 = x11 + x15 | 0; + x07 = rotl(x07 ^ x11, 7); + x00 = x00 + x05 | 0; + x15 = rotl(x15 ^ x00, 16); + x10 = x10 + x15 | 0; + x05 = rotl(x05 ^ x10, 12); + x00 = x00 + x05 | 0; + x15 = rotl(x15 ^ x00, 8); + x10 = x10 + x15 | 0; + x05 = rotl(x05 ^ x10, 7); + x01 = x01 + x06 | 0; + x12 = rotl(x12 ^ x01, 16); + x11 = x11 + x12 | 0; + x06 = rotl(x06 ^ x11, 12); + x01 = x01 + x06 | 0; + x12 = rotl(x12 ^ x01, 8); + x11 = x11 + x12 | 0; + x06 = rotl(x06 ^ x11, 7); + x02 = x02 + x07 | 0; + x13 = rotl(x13 ^ x02, 16); + x08 = x08 + x13 | 0; + x07 = rotl(x07 ^ x08, 12); + x02 = x02 + x07 | 0; + x13 = rotl(x13 ^ x02, 8); + x08 = x08 + x13 | 0; + x07 = rotl(x07 ^ x08, 7); + x03 = x03 + x04 | 0; + x14 = rotl(x14 ^ x03, 16); + x09 = x09 + x14 | 0; + x04 = rotl(x04 ^ x09, 12); + x03 = x03 + x04 | 0; + x14 = rotl(x14 ^ x03, 8); + x09 = x09 + x14 | 0; + x04 = rotl(x04 ^ x09, 7); + } + let oi = 0; + o32[oi++] = x00; + o32[oi++] = x01; + o32[oi++] = x02; + o32[oi++] = x03; + o32[oi++] = x12; + o32[oi++] = x13; + o32[oi++] = x14; + o32[oi++] = x15; + } + var chacha20 = /* @__PURE__ */ createCipher(chachaCore, { + counterRight: false, + counterLength: 4, + allowShortKeys: false + }); + var xchacha20 = /* @__PURE__ */ createCipher(chachaCore, { + counterRight: false, + counterLength: 8, + extendNonceFn: hchacha, + allowShortKeys: false + }); + var ZEROS162 = /* @__PURE__ */ new Uint8Array(16); + var updatePadded = (h, msg) => { + h.update(msg); + const left = msg.length % 16; + if (left) + h.update(ZEROS162.subarray(left)); + }; + var ZEROS322 = /* @__PURE__ */ new Uint8Array(32); + function computeTag2(fn, key, nonce, data, AAD) { + const authKey = fn(key, nonce, ZEROS322); + const h = poly1305.create(authKey); + if (AAD) + updatePadded(h, AAD); + updatePadded(h, data); + const num = new Uint8Array(16); + const view = createView3(num); + setBigUint643(view, 0, BigInt(AAD ? AAD.length : 0), true); + setBigUint643(view, 8, BigInt(data.length), true); + h.update(num); + const res = h.digest(); + authKey.fill(0); + return res; + } + var _poly1305_aead = (xorStream) => (key, nonce, AAD) => { + const tagLength = 16; + bytes3(key, 32); + bytes3(nonce); + return { + encrypt: (plaintext, output4) => { + const plength = plaintext.length; + const clength = plength + tagLength; + if (output4) { + bytes3(output4, clength); + } else { + output4 = new Uint8Array(clength); + } + xorStream(key, nonce, plaintext, output4, 1); + const tag = computeTag2(xorStream, key, nonce, output4.subarray(0, -tagLength), AAD); + output4.set(tag, plength); + return output4; + }, + decrypt: (ciphertext, output4) => { + const clength = ciphertext.length; + const plength = clength - tagLength; + if (clength < tagLength) + throw new Error(`encrypted data must be at least ${tagLength} bytes`); + if (output4) { + bytes3(output4, plength); + } else { + output4 = new Uint8Array(plength); + } + const data = ciphertext.subarray(0, -tagLength); + const passedTag = ciphertext.subarray(-tagLength); + const tag = computeTag2(xorStream, key, nonce, data, AAD); + if (!equalBytes2(passedTag, tag)) + throw new Error("invalid tag"); + xorStream(key, nonce, data, output4, 1); + return output4; + } + }; + }; + var chacha20poly1305 = /* @__PURE__ */ wrapCipher({ blockSize: 64, nonceLength: 12, tagLength: 16 }, _poly1305_aead(chacha20)); + var xchacha20poly1305 = /* @__PURE__ */ wrapCipher({ blockSize: 64, nonceLength: 24, tagLength: 16 }, _poly1305_aead(xchacha20)); + + // node_modules/@noble/hashes/esm/hmac.js + var HMAC2 = class extends Hash2 { + constructor(hash3, _key) { + super(); + this.finished = false; + this.destroyed = false; + assert_default.hash(hash3); + const key = toBytes2(_key); + this.iHash = hash3.create(); + if (typeof this.iHash.update !== "function") + throw new Error("Expected instance of class which extends utils.Hash"); + this.blockLen = this.iHash.blockLen; + this.outputLen = this.iHash.outputLen; + const blockLen = this.blockLen; + const pad2 = new Uint8Array(blockLen); + pad2.set(key.length > blockLen ? hash3.create().update(key).digest() : key); + for (let i2 = 0; i2 < pad2.length; i2++) + pad2[i2] ^= 54; + this.iHash.update(pad2); + this.oHash = hash3.create(); + for (let i2 = 0; i2 < pad2.length; i2++) + pad2[i2] ^= 54 ^ 92; + this.oHash.update(pad2); + pad2.fill(0); + } + update(buf) { + assert_default.exists(this); + this.iHash.update(buf); + return this; + } + digestInto(out) { + assert_default.exists(this); + assert_default.bytes(out, this.outputLen); + this.finished = true; + this.iHash.digestInto(out); + this.oHash.update(out); + this.oHash.digestInto(out); + this.destroy(); + } + digest() { + const out = new Uint8Array(this.oHash.outputLen); + this.digestInto(out); + return out; + } + _cloneInto(to) { + to || (to = Object.create(Object.getPrototypeOf(this), {})); + const { oHash, iHash, finished, destroyed, blockLen, outputLen } = this; + to = to; + to.finished = finished; + to.destroyed = destroyed; + to.blockLen = blockLen; + to.outputLen = outputLen; + to.oHash = oHash._cloneInto(to.oHash); + to.iHash = iHash._cloneInto(to.iHash); + return to; + } + destroy() { + this.destroyed = true; + this.oHash.destroy(); + this.iHash.destroy(); + } + }; + var hmac2 = (hash3, key, message) => new HMAC2(hash3, key).update(message).digest(); + hmac2.create = (hash3, key) => new HMAC2(hash3, key); + + // node_modules/@noble/hashes/esm/hkdf.js + function extract(hash3, ikm, salt) { + assert_default.hash(hash3); + if (salt === void 0) + salt = new Uint8Array(hash3.outputLen); + return hmac2(hash3, toBytes2(salt), toBytes2(ikm)); + } + var HKDF_COUNTER = new Uint8Array([0]); + var EMPTY_BUFFER = new Uint8Array(); + function expand(hash3, prk, info, length = 32) { + assert_default.hash(hash3); + assert_default.number(length); + if (length > 255 * hash3.outputLen) + throw new Error("Length should be <= 255*HashLen"); + const blocks = Math.ceil(length / hash3.outputLen); + if (info === void 0) + info = EMPTY_BUFFER; + const okm = new Uint8Array(blocks * hash3.outputLen); + const HMAC3 = hmac2.create(hash3, prk); + const HMACTmp = HMAC3._cloneInto(); + const T = new Uint8Array(HMAC3.outputLen); + for (let counter = 0; counter < blocks; counter++) { + HKDF_COUNTER[0] = counter + 1; + HMACTmp.update(counter === 0 ? EMPTY_BUFFER : T).update(info).update(HKDF_COUNTER).digestInto(T); + okm.set(T, hash3.outputLen * counter); + HMAC3._cloneInto(HMACTmp); + } + HMAC3.destroy(); + HMACTmp.destroy(); + T.fill(0); + HKDF_COUNTER.fill(0); + return okm.slice(0, length); + } + + // nip44.ts + var minPlaintextSize = 1; + var maxPlaintextSize = 65535; + function getConversationKey(privkeyA, pubkeyB) { + const sharedX = secp256k1.getSharedSecret(privkeyA, "02" + pubkeyB).subarray(1, 33); + return extract(sha2562, sharedX, "nip44-v2"); + } + function getMessageKeys(conversationKey, nonce) { + const keys = expand(sha2562, conversationKey, nonce, 76); + return { + chacha_key: keys.subarray(0, 32), + chacha_nonce: keys.subarray(32, 44), + hmac_key: keys.subarray(44, 76) + }; + } + function calcPaddedLen(len) { + if (!Number.isSafeInteger(len) || len < 1) + throw new Error("expected positive integer"); + if (len <= 32) + return 32; + const nextPower = 1 << Math.floor(Math.log2(len - 1)) + 1; + const chunk = nextPower <= 256 ? 32 : nextPower / 8; + return chunk * (Math.floor((len - 1) / chunk) + 1); + } + function writeU16BE(num) { + if (!Number.isSafeInteger(num) || num < minPlaintextSize || num > maxPlaintextSize) + throw new Error("invalid plaintext size: must be between 1 and 65535 bytes"); + const arr = new Uint8Array(2); + new DataView(arr.buffer).setUint16(0, num, false); + return arr; + } + function pad(plaintext) { + const unpadded = utf8Encoder.encode(plaintext); + const unpaddedLen = unpadded.length; + const prefix = writeU16BE(unpaddedLen); + const suffix = new Uint8Array(calcPaddedLen(unpaddedLen) - unpaddedLen); + return concatBytes3(prefix, unpadded, suffix); + } + function unpad(padded) { + const unpaddedLen = new DataView(padded.buffer).getUint16(0); + const unpadded = padded.subarray(2, 2 + unpaddedLen); + if (unpaddedLen < minPlaintextSize || unpaddedLen > maxPlaintextSize || unpadded.length !== unpaddedLen || padded.length !== 2 + calcPaddedLen(unpaddedLen)) + throw new Error("invalid padding"); + return utf8Decoder.decode(unpadded); + } + function hmacAad(key, message, aad) { + if (aad.length !== 32) + throw new Error("AAD associated data must be 32 bytes"); + const combined = concatBytes3(aad, message); + return hmac2(sha2562, key, combined); + } + function decodePayload(payload) { + if (typeof payload !== "string") + throw new Error("payload must be a valid string"); + const plen = payload.length; + if (plen < 132 || plen > 87472) + throw new Error("invalid payload length: " + plen); + if (payload[0] === "#") + throw new Error("unknown encryption version"); + let data; + try { + data = base64.decode(payload); + } catch (error) { + throw new Error("invalid base64: " + error.message); + } + const dlen = data.length; + if (dlen < 99 || dlen > 65603) + throw new Error("invalid data length: " + dlen); + const vers = data[0]; + if (vers !== 2) + throw new Error("unknown encryption version " + vers); + return { + nonce: data.subarray(1, 33), + ciphertext: data.subarray(33, -32), + mac: data.subarray(-32) + }; + } + function encrypt3(plaintext, conversationKey, nonce = randomBytes2(32)) { + const { chacha_key, chacha_nonce, hmac_key } = getMessageKeys(conversationKey, nonce); + const padded = pad(plaintext); + const ciphertext = chacha20(chacha_key, chacha_nonce, padded); + const mac = hmacAad(hmac_key, ciphertext, nonce); + return base64.encode(concatBytes3(new Uint8Array([2]), nonce, ciphertext, mac)); + } + function decrypt3(payload, conversationKey) { + const { nonce, ciphertext, mac } = decodePayload(payload); + const { chacha_key, chacha_nonce, hmac_key } = getMessageKeys(conversationKey, nonce); + const calculatedMac = hmacAad(hmac_key, ciphertext, nonce); + if (!equalBytes2(calculatedMac, mac)) + throw new Error("invalid MAC"); + const padded = chacha20(chacha_key, chacha_nonce, ciphertext); + return unpad(padded); + } + var v2 = { + utils: { + getConversationKey, + calcPaddedLen + }, + encrypt: encrypt3, + decrypt: decrypt3 + }; + + // nip59.ts + var TWO_DAYS = 2 * 24 * 60 * 60; + var now = () => Math.round(Date.now() / 1e3); + var randomNow = () => Math.round(now() - Math.random() * TWO_DAYS); + var nip44ConversationKey = (privateKey, publicKey) => getConversationKey(privateKey, publicKey); + var nip44Encrypt = (data, privateKey, publicKey) => encrypt3(JSON.stringify(data), nip44ConversationKey(privateKey, publicKey)); + var nip44Decrypt = (data, privateKey) => JSON.parse(decrypt3(data.content, nip44ConversationKey(privateKey, data.pubkey))); + function createRumor(event, privateKey) { + const rumor = { + created_at: now(), + content: "", + tags: [], + ...event, + pubkey: getPublicKey(privateKey) + }; + rumor.id = getEventHash(rumor); + return rumor; + } + function createSeal(rumor, privateKey, recipientPublicKey) { + return finalizeEvent( + { + kind: Seal, + content: nip44Encrypt(rumor, privateKey, recipientPublicKey), + created_at: randomNow(), + tags: [] + }, + privateKey + ); + } + function createWrap(seal, recipientPublicKey) { + const randomKey = generateSecretKey(); + return finalizeEvent( + { + kind: GiftWrap, + content: nip44Encrypt(seal, randomKey, recipientPublicKey), + created_at: randomNow(), + tags: [["p", recipientPublicKey]] + }, + randomKey + ); + } + function wrapEvent(event, senderPrivateKey, recipientPublicKey) { + const rumor = createRumor(event, senderPrivateKey); + const seal = createSeal(rumor, senderPrivateKey, recipientPublicKey); + return createWrap(seal, recipientPublicKey); + } + function wrapManyEvents(event, senderPrivateKey, recipientsPublicKeys) { + if (!recipientsPublicKeys || recipientsPublicKeys.length === 0) { + throw new Error("At least one recipient is required."); + } + const senderPublicKey = getPublicKey(senderPrivateKey); + const wrappeds = [wrapEvent(event, senderPrivateKey, senderPublicKey)]; + recipientsPublicKeys.forEach((recipientPublicKey) => { + wrappeds.push(wrapEvent(event, senderPrivateKey, recipientPublicKey)); + }); + return wrappeds; + } + function unwrapEvent(wrap, recipientPrivateKey) { + const unwrappedSeal = nip44Decrypt(wrap, recipientPrivateKey); + return nip44Decrypt(unwrappedSeal, recipientPrivateKey); + } + function unwrapManyEvents(wrappedEvents, recipientPrivateKey) { + let unwrappedEvents = []; + wrappedEvents.forEach((e) => { + unwrappedEvents.push(unwrapEvent(e, recipientPrivateKey)); + }); + unwrappedEvents.sort((a, b) => a.created_at - b.created_at); + return unwrappedEvents; + } + + // nip17.ts + function createEvent(recipients, message, conversationTitle, replyTo) { + const baseEvent = { + created_at: Math.ceil(Date.now() / 1e3), + kind: PrivateDirectMessage, + tags: [], + content: message + }; + const recipientsArray = Array.isArray(recipients) ? recipients : [recipients]; + recipientsArray.forEach(({ publicKey, relayUrl }) => { + baseEvent.tags.push(relayUrl ? ["p", publicKey, relayUrl] : ["p", publicKey]); + }); + if (replyTo) { + baseEvent.tags.push(["e", replyTo.eventId, replyTo.relayUrl || "", "reply"]); + } + if (conversationTitle) { + baseEvent.tags.push(["subject", conversationTitle]); + } + return baseEvent; + } + function wrapEvent2(senderPrivateKey, recipient, message, conversationTitle, replyTo) { + const event = createEvent(recipient, message, conversationTitle, replyTo); + return wrapEvent(event, senderPrivateKey, recipient.publicKey); + } + function wrapManyEvents2(senderPrivateKey, recipients, message, conversationTitle, replyTo) { + if (!recipients || recipients.length === 0) { + throw new Error("At least one recipient is required."); + } + const senderPublicKey = getPublicKey(senderPrivateKey); + return [{ publicKey: senderPublicKey }, ...recipients].map( + (recipient) => wrapEvent2(senderPrivateKey, recipient, message, conversationTitle, replyTo) + ); + } + var unwrapEvent2 = unwrapEvent; + var unwrapManyEvents2 = unwrapManyEvents; + + // nip18.ts + var nip18_exports = {}; + __export(nip18_exports, { + finishRepostEvent: () => finishRepostEvent, + getRepostedEvent: () => getRepostedEvent, + getRepostedEventPointer: () => getRepostedEventPointer + }); + function finishRepostEvent(t, reposted, relayUrl, privateKey) { + let kind; + const tags = [...t.tags ?? [], ["e", reposted.id, relayUrl], ["p", reposted.pubkey]]; + if (reposted.kind === ShortTextNote) { + kind = Repost; + } else { + kind = GenericRepost; + tags.push(["k", String(reposted.kind)]); + } + return finalizeEvent( + { + kind, + tags, + content: t.content === "" || reposted.tags?.find((tag) => tag[0] === "-") ? "" : JSON.stringify(reposted), + created_at: t.created_at + }, + privateKey + ); + } + function getRepostedEventPointer(event) { + if (![Repost, GenericRepost].includes(event.kind)) { + return void 0; + } + let lastETag; + let lastPTag; + for (let i2 = event.tags.length - 1; i2 >= 0 && (lastETag === void 0 || lastPTag === void 0); i2--) { + const tag = event.tags[i2]; + if (tag.length >= 2) { + if (tag[0] === "e" && lastETag === void 0) { + lastETag = tag; + } else if (tag[0] === "p" && lastPTag === void 0) { + lastPTag = tag; + } + } + } + if (lastETag === void 0) { + return void 0; + } + return { + id: lastETag[1], + relays: [lastETag[2], lastPTag?.[2]].filter((x) => typeof x === "string"), + author: lastPTag?.[1] + }; + } + function getRepostedEvent(event, { skipVerification } = {}) { + const pointer = getRepostedEventPointer(event); + if (pointer === void 0 || event.content === "") { + return void 0; + } + let repostedEvent; + try { + repostedEvent = JSON.parse(event.content); + } catch (error) { + return void 0; + } + if (repostedEvent.id !== pointer.id) { + return void 0; + } + if (!skipVerification && !verifyEvent(repostedEvent)) { + return void 0; + } + return repostedEvent; + } + + // nip21.ts + var nip21_exports = {}; + __export(nip21_exports, { + NOSTR_URI_REGEX: () => NOSTR_URI_REGEX, + parse: () => parse2, + test: () => test + }); + var NOSTR_URI_REGEX = new RegExp(`nostr:(${BECH32_REGEX.source})`); + function test(value) { + return typeof value === "string" && new RegExp(`^${NOSTR_URI_REGEX.source}$`).test(value); + } + function parse2(uri) { + const match = uri.match(new RegExp(`^${NOSTR_URI_REGEX.source}$`)); + if (!match) + throw new Error(`Invalid Nostr URI: ${uri}`); + return { + uri: match[0], + value: match[1], + decoded: decode(match[1]) + }; + } + + // nip25.ts + var nip25_exports = {}; + __export(nip25_exports, { + finishReactionEvent: () => finishReactionEvent, + getReactedEventPointer: () => getReactedEventPointer + }); + function finishReactionEvent(t, reacted, privateKey) { + const inheritedTags = reacted.tags.filter((tag) => tag.length >= 2 && (tag[0] === "e" || tag[0] === "p")); + return finalizeEvent( + { + ...t, + kind: Reaction, + tags: [...t.tags ?? [], ...inheritedTags, ["e", reacted.id], ["p", reacted.pubkey]], + content: t.content ?? "+" + }, + privateKey + ); + } + function getReactedEventPointer(event) { + if (event.kind !== Reaction) { + return void 0; + } + let lastETag; + let lastPTag; + for (let i2 = event.tags.length - 1; i2 >= 0 && (lastETag === void 0 || lastPTag === void 0); i2--) { + const tag = event.tags[i2]; + if (tag.length >= 2) { + if (tag[0] === "e" && lastETag === void 0) { + lastETag = tag; + } else if (tag[0] === "p" && lastPTag === void 0) { + lastPTag = tag; + } + } + } + if (lastETag === void 0 || lastPTag === void 0) { + return void 0; + } + return { + id: lastETag[1], + relays: [lastETag[2], lastPTag[2]].filter((x) => x !== void 0), + author: lastPTag[1] + }; + } + + // nip27.ts + var nip27_exports = {}; + __export(nip27_exports, { + parse: () => parse3 + }); + var noCharacter = /\W/m; + var noURLCharacter = /\W |\W$|$|,| /m; + function* parse3(content) { + const max = content.length; + let prevIndex = 0; + let index = 0; + while (index < max) { + let u = content.indexOf(":", index); + if (u === -1) { + break; + } + if (content.substring(u - 5, u) === "nostr") { + const m = content.substring(u + 60).match(noCharacter); + const end = m ? u + 60 + m.index : max; + try { + let pointer; + let { data, type } = decode(content.substring(u + 1, end)); + switch (type) { + case "npub": + pointer = { pubkey: data }; + break; + case "nsec": + case "note": + index = end + 1; + continue; + default: + pointer = data; + } + if (prevIndex !== u - 5) { + yield { type: "text", text: content.substring(prevIndex, u - 5) }; + } + yield { type: "reference", pointer }; + index = end; + prevIndex = index; + continue; + } catch (_err) { + index = u + 1; + continue; + } + } else if (content.substring(u - 5, u) === "https" || content.substring(u - 4, u) === "http") { + const m = content.substring(u + 4).match(noURLCharacter); + const end = m ? u + 4 + m.index : max; + const prefixLen = content[u - 1] === "s" ? 5 : 4; + try { + let url = new URL(content.substring(u - prefixLen, end)); + if (url.hostname.indexOf(".") === -1) { + throw new Error("invalid url"); + } + if (prevIndex !== u - prefixLen) { + yield { type: "text", text: content.substring(prevIndex, u - prefixLen) }; + } + if (url.pathname.endsWith(".png") || url.pathname.endsWith(".jpg") || url.pathname.endsWith(".jpeg") || url.pathname.endsWith(".gif") || url.pathname.endsWith(".webp")) { + yield { type: "image", url: url.toString() }; + index = end; + prevIndex = index; + continue; + } + if (url.pathname.endsWith(".mp4") || url.pathname.endsWith(".avi") || url.pathname.endsWith(".webm") || url.pathname.endsWith(".mkv")) { + yield { type: "video", url: url.toString() }; + index = end; + prevIndex = index; + continue; + } + if (url.pathname.endsWith(".mp3") || url.pathname.endsWith(".aac") || url.pathname.endsWith(".ogg") || url.pathname.endsWith(".opus")) { + yield { type: "audio", url: url.toString() }; + index = end; + prevIndex = index; + continue; + } + yield { type: "url", url: url.toString() }; + index = end; + prevIndex = index; + continue; + } catch (_err) { + index = end + 1; + continue; + } + } else if (content.substring(u - 3, u) === "wss" || content.substring(u - 2, u) === "ws") { + const m = content.substring(u + 4).match(noURLCharacter); + const end = m ? u + 4 + m.index : max; + const prefixLen = content[u - 1] === "s" ? 3 : 2; + try { + let url = new URL(content.substring(u - prefixLen, end)); + if (url.hostname.indexOf(".") === -1) { + throw new Error("invalid ws url"); + } + if (prevIndex !== u - prefixLen) { + yield { type: "text", text: content.substring(prevIndex, u - prefixLen) }; + } + yield { type: "relay", url: url.toString() }; + index = end; + prevIndex = index; + continue; + } catch (_err) { + index = end + 1; + continue; + } + } else { + index = u + 1; + continue; + } + } + if (prevIndex !== max) { + yield { type: "text", text: content.substring(prevIndex) }; + } + } + + // nip28.ts + var nip28_exports = {}; + __export(nip28_exports, { + channelCreateEvent: () => channelCreateEvent, + channelHideMessageEvent: () => channelHideMessageEvent, + channelMessageEvent: () => channelMessageEvent, + channelMetadataEvent: () => channelMetadataEvent, + channelMuteUserEvent: () => channelMuteUserEvent + }); + var channelCreateEvent = (t, privateKey) => { + let content; + if (typeof t.content === "object") { + content = JSON.stringify(t.content); + } else if (typeof t.content === "string") { + content = t.content; + } else { + return void 0; + } + return finalizeEvent( + { + kind: ChannelCreation, + tags: [...t.tags ?? []], + content, + created_at: t.created_at + }, + privateKey + ); + }; + var channelMetadataEvent = (t, privateKey) => { + let content; + if (typeof t.content === "object") { + content = JSON.stringify(t.content); + } else if (typeof t.content === "string") { + content = t.content; + } else { + return void 0; + } + return finalizeEvent( + { + kind: ChannelMetadata, + tags: [["e", t.channel_create_event_id], ...t.tags ?? []], + content, + created_at: t.created_at + }, + privateKey + ); + }; + var channelMessageEvent = (t, privateKey) => { + const tags = [["e", t.channel_create_event_id, t.relay_url, "root"]]; + if (t.reply_to_channel_message_event_id) { + tags.push(["e", t.reply_to_channel_message_event_id, t.relay_url, "reply"]); + } + return finalizeEvent( + { + kind: ChannelMessage, + tags: [...tags, ...t.tags ?? []], + content: t.content, + created_at: t.created_at + }, + privateKey + ); + }; + var channelHideMessageEvent = (t, privateKey) => { + let content; + if (typeof t.content === "object") { + content = JSON.stringify(t.content); + } else if (typeof t.content === "string") { + content = t.content; + } else { + return void 0; + } + return finalizeEvent( + { + kind: ChannelHideMessage, + tags: [["e", t.channel_message_event_id], ...t.tags ?? []], + content, + created_at: t.created_at + }, + privateKey + ); + }; + var channelMuteUserEvent = (t, privateKey) => { + let content; + if (typeof t.content === "object") { + content = JSON.stringify(t.content); + } else if (typeof t.content === "string") { + content = t.content; + } else { + return void 0; + } + return finalizeEvent( + { + kind: ChannelMuteUser, + tags: [["p", t.pubkey_to_mute], ...t.tags ?? []], + content, + created_at: t.created_at + }, + privateKey + ); + }; + + // nip30.ts + var nip30_exports = {}; + __export(nip30_exports, { + EMOJI_SHORTCODE_REGEX: () => EMOJI_SHORTCODE_REGEX, + matchAll: () => matchAll, + regex: () => regex, + replaceAll: () => replaceAll + }); + var EMOJI_SHORTCODE_REGEX = /:(\w+):/; + var regex = () => new RegExp(`\\B${EMOJI_SHORTCODE_REGEX.source}\\B`, "g"); + function* matchAll(content) { + const matches = content.matchAll(regex()); + for (const match of matches) { + try { + const [shortcode, name] = match; + yield { + shortcode, + name, + start: match.index, + end: match.index + shortcode.length + }; + } catch (_e) { + } + } + } + function replaceAll(content, replacer) { + return content.replaceAll(regex(), (shortcode, name) => { + return replacer({ + shortcode, + name + }); + }); + } + + // nip39.ts + var nip39_exports = {}; + __export(nip39_exports, { + useFetchImplementation: () => useFetchImplementation3, + validateGithub: () => validateGithub + }); + var _fetch3; + try { + _fetch3 = fetch; + } catch { + } + function useFetchImplementation3(fetchImplementation) { + _fetch3 = fetchImplementation; + } + async function validateGithub(pubkey, username, proof) { + try { + let res = await (await _fetch3(`https://gist.github.com/${username}/${proof}/raw`)).text(); + return res === `Verifying that I control the following Nostr public key: ${pubkey}`; + } catch (_) { + return false; + } + } + + // nip47.ts + var nip47_exports = {}; + __export(nip47_exports, { + makeNwcRequestEvent: () => makeNwcRequestEvent, + parseConnectionString: () => parseConnectionString + }); + function parseConnectionString(connectionString) { + const { host, pathname, searchParams } = new URL(connectionString); + const pubkey = pathname || host; + const relay = searchParams.get("relay"); + const secret = searchParams.get("secret"); + if (!pubkey || !relay || !secret) { + throw new Error("invalid connection string"); + } + return { pubkey, relay, secret }; + } + async function makeNwcRequestEvent(pubkey, secretKey, invoice) { + const content = { + method: "pay_invoice", + params: { + invoice + } + }; + const encryptedContent = encrypt2(secretKey, pubkey, JSON.stringify(content)); + const eventTemplate = { + kind: NWCWalletRequest, + created_at: Math.round(Date.now() / 1e3), + content: encryptedContent, + tags: [["p", pubkey]] + }; + return finalizeEvent(eventTemplate, secretKey); + } + + // nip54.ts + var nip54_exports = {}; + __export(nip54_exports, { + normalizeIdentifier: () => normalizeIdentifier + }); + function normalizeIdentifier(name) { + name = name.trim().toLowerCase(); + name = name.normalize("NFKC"); + return Array.from(name).map((char) => { + if (/\p{Letter}/u.test(char) || /\p{Number}/u.test(char)) { + return char; + } + return "-"; + }).join(""); + } + + // nip57.ts + var nip57_exports = {}; + __export(nip57_exports, { + getSatoshisAmountFromBolt11: () => getSatoshisAmountFromBolt11, + getZapEndpoint: () => getZapEndpoint, + makeZapReceipt: () => makeZapReceipt, + makeZapRequest: () => makeZapRequest, + useFetchImplementation: () => useFetchImplementation4, + validateZapRequest: () => validateZapRequest + }); + var _fetch4; + try { + _fetch4 = fetch; + } catch { + } + function useFetchImplementation4(fetchImplementation) { + _fetch4 = fetchImplementation; + } + async function getZapEndpoint(metadata) { + try { + let lnurl = ""; + let { lud06, lud16 } = JSON.parse(metadata.content); + if (lud06) { + let { words } = bech32.decode(lud06, 1e3); + let data = bech32.fromWords(words); + lnurl = utf8Decoder.decode(data); + } else if (lud16) { + let [name, domain] = lud16.split("@"); + lnurl = new URL(`/.well-known/lnurlp/${name}`, `https://${domain}`).toString(); + } else { + return null; + } + let res = await _fetch4(lnurl); + let body = await res.json(); + if (body.allowsNostr && body.nostrPubkey) { + return body.callback; + } + } catch (err) { + } + return null; + } + function makeZapRequest(params) { + let zr = { + kind: 9734, + created_at: Math.round(Date.now() / 1e3), + content: params.comment || "", + tags: [ + ["p", "pubkey" in params ? params.pubkey : params.event.pubkey], + ["amount", params.amount.toString()], + ["relays", ...params.relays] + ] + }; + if ("event" in params) { + zr.tags.push(["e", params.event.id]); + if (isReplaceableKind(params.event.kind)) { + const a = ["a", `${params.event.kind}:${params.event.pubkey}:`]; + zr.tags.push(a); + } else if (isAddressableKind(params.event.kind)) { + let d = params.event.tags.find(([t, v]) => t === "d" && v); + if (!d) + throw new Error("d tag not found or is empty"); + const a = ["a", `${params.event.kind}:${params.event.pubkey}:${d[1]}`]; + zr.tags.push(a); + } + zr.tags.push(["k", params.event.kind.toString()]); + } + return zr; + } + function validateZapRequest(zapRequestString) { + let zapRequest; + try { + zapRequest = JSON.parse(zapRequestString); + } catch (err) { + return "Invalid zap request JSON."; + } + if (!validateEvent(zapRequest)) + return "Zap request is not a valid Nostr event."; + if (!verifyEvent(zapRequest)) + return "Invalid signature on zap request."; + let p = zapRequest.tags.find(([t, v]) => t === "p" && v); + if (!p) + return "Zap request doesn't have a 'p' tag."; + if (!p[1].match(/^[a-f0-9]{64}$/)) + return "Zap request 'p' tag is not valid hex."; + let e = zapRequest.tags.find(([t, v]) => t === "e" && v); + if (e && !e[1].match(/^[a-f0-9]{64}$/)) + return "Zap request 'e' tag is not valid hex."; + let relays = zapRequest.tags.find(([t, v]) => t === "relays" && v); + if (!relays) + return "Zap request doesn't have a 'relays' tag."; + return null; + } + function makeZapReceipt({ + zapRequest, + preimage, + bolt11, + paidAt + }) { + let zr = JSON.parse(zapRequest); + let tagsFromZapRequest = zr.tags.filter(([t]) => t === "e" || t === "p" || t === "a"); + let zap = { + kind: 9735, + created_at: Math.round(paidAt.getTime() / 1e3), + content: "", + tags: [...tagsFromZapRequest, ["P", zr.pubkey], ["bolt11", bolt11], ["description", zapRequest]] + }; + if (preimage) { + zap.tags.push(["preimage", preimage]); + } + return zap; + } + function getSatoshisAmountFromBolt11(bolt11) { + if (bolt11.length < 50) { + return 0; + } + bolt11 = bolt11.substring(0, 50); + const idx = bolt11.lastIndexOf("1"); + if (idx === -1) { + return 0; + } + const hrp = bolt11.substring(0, idx); + if (!hrp.startsWith("lnbc")) { + return 0; + } + const amount = hrp.substring(4); + if (amount.length < 1) { + return 0; + } + const char = amount[amount.length - 1]; + const digit = char.charCodeAt(0) - "0".charCodeAt(0); + const isDigit = digit >= 0 && digit <= 9; + let cutPoint = amount.length - 1; + if (isDigit) { + cutPoint++; + } + if (cutPoint < 1) { + return 0; + } + const num = parseInt(amount.substring(0, cutPoint)); + switch (char) { + case "m": + return num * 1e5; + case "u": + return num * 100; + case "n": + return num / 10; + case "p": + return num / 1e4; + default: + return num * 1e8; + } + } + + // nip98.ts + var nip98_exports = {}; + __export(nip98_exports, { + getToken: () => getToken, + hashPayload: () => hashPayload, + unpackEventFromToken: () => unpackEventFromToken, + validateEvent: () => validateEvent2, + validateEventKind: () => validateEventKind, + validateEventMethodTag: () => validateEventMethodTag, + validateEventPayloadTag: () => validateEventPayloadTag, + validateEventTimestamp: () => validateEventTimestamp, + validateEventUrlTag: () => validateEventUrlTag, + validateToken: () => validateToken + }); + var _authorizationScheme = "Nostr "; + async function getToken(loginUrl, httpMethod, sign, includeAuthorizationScheme = false, payload) { + const event = { + kind: HTTPAuth, + tags: [ + ["u", loginUrl], + ["method", httpMethod] + ], + created_at: Math.round(new Date().getTime() / 1e3), + content: "" + }; + if (payload) { + event.tags.push(["payload", hashPayload(payload)]); + } + const signedEvent = await sign(event); + const authorizationScheme = includeAuthorizationScheme ? _authorizationScheme : ""; + return authorizationScheme + base64.encode(utf8Encoder.encode(JSON.stringify(signedEvent))); + } + async function validateToken(token, url, method) { + const event = await unpackEventFromToken(token).catch((error) => { + throw error; + }); + const valid = await validateEvent2(event, url, method).catch((error) => { + throw error; + }); + return valid; + } + async function unpackEventFromToken(token) { + if (!token) { + throw new Error("Missing token"); + } + token = token.replace(_authorizationScheme, ""); + const eventB64 = utf8Decoder.decode(base64.decode(token)); + if (!eventB64 || eventB64.length === 0 || !eventB64.startsWith("{")) { + throw new Error("Invalid token"); + } + const event = JSON.parse(eventB64); + return event; + } + function validateEventTimestamp(event) { + if (!event.created_at) { + return false; + } + return Math.round(new Date().getTime() / 1e3) - event.created_at < 60; + } + function validateEventKind(event) { + return event.kind === HTTPAuth; + } + function validateEventUrlTag(event, url) { + const urlTag = event.tags.find((t) => t[0] === "u"); + if (!urlTag) { + return false; + } + return urlTag.length > 0 && urlTag[1] === url; + } + function validateEventMethodTag(event, method) { + const methodTag = event.tags.find((t) => t[0] === "method"); + if (!methodTag) { + return false; + } + return methodTag.length > 0 && methodTag[1].toLowerCase() === method.toLowerCase(); + } + function hashPayload(payload) { + const hash3 = sha2562(utf8Encoder.encode(JSON.stringify(payload))); + return bytesToHex2(hash3); + } + function validateEventPayloadTag(event, payload) { + const payloadTag = event.tags.find((t) => t[0] === "payload"); + if (!payloadTag) { + return false; + } + const payloadHash = hashPayload(payload); + return payloadTag.length > 0 && payloadTag[1] === payloadHash; + } + async function validateEvent2(event, url, method, body) { + if (!verifyEvent(event)) { + throw new Error("Invalid nostr event, signature invalid"); + } + if (!validateEventKind(event)) { + throw new Error("Invalid nostr event, kind invalid"); + } + if (!validateEventTimestamp(event)) { + throw new Error("Invalid nostr event, created_at timestamp invalid"); + } + if (!validateEventUrlTag(event, url)) { + throw new Error("Invalid nostr event, url tag invalid"); + } + if (!validateEventMethodTag(event, method)) { + throw new Error("Invalid nostr event, method tag invalid"); + } + if (Boolean(body) && typeof body === "object" && Object.keys(body).length > 0) { + if (!validateEventPayloadTag(event, body)) { + throw new Error("Invalid nostr event, payload tag does not match request body hash"); + } + } + return true; + } + return __toCommonJS(nostr_tools_exports); +})(); diff --git a/lite/ui/modal.js b/lite/ui/modal.js new file mode 100644 index 0000000..71cf7f5 --- /dev/null +++ b/lite/ui/modal.js @@ -0,0 +1,1009 @@ +/** + * NOSTR_LOGIN_LITE Modal UI + * Minimal vanilla JS modal to replace Stencil/Tailwind component library + */ + +class Modal { + constructor(options) { + this.options = options; + this.container = null; + this.isVisible = false; + this.currentScreen = null; + + // Initialize modal container and styles + this._initModal(); + } + + _initModal() { + // Create modal container + this.container = document.createElement('div'); + this.container.id = 'nl-modal'; + this.container.style.cssText = ` + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.75); + display: none; + z-index: 10000; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + `; + + // Create modal content + const modalContent = document.createElement('div'); + modalContent.style.cssText = ` + position: relative; + background: white; + width: 90%; + max-width: 400px; + margin: 50px auto; + border-radius: 12px; + box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); + max-height: 600px; + overflow: hidden; + `; + + // Header + const modalHeader = document.createElement('div'); + modalHeader.style.cssText = ` + padding: 20px 24px 0 24px; + display: flex; + justify-content: space-between; + align-items: center; + `; + + const modalTitle = document.createElement('h2'); + modalTitle.textContent = 'Nostr Login'; + modalTitle.style.cssText = ` + margin: 0; + font-size: 24px; + font-weight: 600; + color: #1f2937; + `; + + const closeButton = document.createElement('button'); + closeButton.innerHTML = '×'; + closeButton.onclick = () => this.close(); + closeButton.style.cssText = ` + background: none; + border: none; + font-size: 28px; + color: #6b7280; + cursor: pointer; + padding: 0; + width: 32px; + height: 32px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 6px; + `; + closeButton.onmouseover = () => closeButton.style.background = '#f3f4f6'; + closeButton.onmouseout = () => closeButton.style.background = 'none'; + + modalHeader.appendChild(modalTitle); + modalHeader.appendChild(closeButton); + + // Body + this.modalBody = document.createElement('div'); + this.modalBody.style.cssText = ` + padding: 24px; + overflow-y: auto; + max-height: 500px; + `; + + modalContent.appendChild(modalHeader); + modalContent.appendChild(this.modalBody); + this.container.appendChild(modalContent); + + // Add to body + document.body.appendChild(this.container); + + // Click outside to close + this.container.onclick = (e) => { + if (e.target === this.container) { + this.close(); + } + }; + + // Update theme + this.updateTheme(); + } + + updateTheme() { + const isDark = this.options?.darkMode; + const modalContent = this.container.querySelector(':nth-child(1)'); + const title = this.container.querySelector('h2'); + + if (isDark) { + modalContent.style.background = '#1f2937'; + title.style.color = 'white'; + } else { + modalContent.style.background = 'white'; + title.style.color = '#1f2937'; + } + } + + open(opts = {}) { + this.currentScreen = opts.startScreen; + this.isVisible = true; + this.container.style.display = 'block'; + + // Render login options + this._renderLoginOptions(); + } + + close() { + this.isVisible = false; + this.container.style.display = 'none'; + this.modalBody.innerHTML = ''; + } + + _renderLoginOptions() { + this.modalBody.innerHTML = ''; + + const options = []; + + // Extension option + if (this.options?.methods?.extension !== false) { + options.push({ + type: 'extension', + title: 'Browser Extension', + description: 'Use your browser extension', + icon: '🔌' + }); + } + + // Local key option + if (this.options?.methods?.local !== false) { + options.push({ + type: 'local', + title: 'Local Key', + description: 'Create or import your own key', + icon: '🔑' + }); + } + + // Nostr Connect option + if (this.options?.methods?.connect !== false) { + options.push({ + type: 'connect', + title: 'Nostr Connect', + description: 'Connect with external signer', + icon: '🌐' + }); + } + + // Read-only option + if (this.options?.methods?.readonly !== false) { + options.push({ + type: 'readonly', + title: 'Read Only', + description: 'Browse without signing', + icon: '👁️' + }); + } + + // OTP/DM option + if (this.options?.methods?.otp !== false && this.options?.otp) { + options.push({ + type: 'otp', + title: 'DM/OTP', + description: 'Receive OTP via DM', + icon: '📱' + }); + } + + // Render each option + options.forEach(option => { + const button = document.createElement('button'); + button.onclick = () => this._handleOptionClick(option.type); + button.style.cssText = ` + display: flex; + align-items: center; + width: 100%; + padding: 16px; + margin-bottom: 12px; + background: ${this.options?.darkMode ? '#374151' : 'white'}; + border: 1px solid ${this.options?.darkMode ? '#4b5563' : '#d1d5db'}; + border-radius: 8px; + cursor: pointer; + transition: all 0.2s; + `; + button.onmouseover = () => { + button.style.boxShadow = '0 4px 6px -1px rgba(0, 0, 0, 0.1)'; + }; + button.onmouseout = () => { + button.style.boxShadow = 'none'; + }; + + const iconDiv = document.createElement('div'); + iconDiv.textContent = option.icon; + iconDiv.style.cssText = ` + font-size: 24px; + margin-right: 16px; + width: 24px; + text-align: center; + `; + + const contentDiv = document.createElement('div'); + contentDiv.style.cssText = 'flex: 1; text-align: left;'; + + const titleDiv = document.createElement('div'); + titleDiv.textContent = option.title; + titleDiv.style.cssText = ` + font-weight: 600; + margin-bottom: 4px; + color: ${this.options?.darkMode ? 'white' : '#1f2937'}; + `; + + const descDiv = document.createElement('div'); + descDiv.textContent = option.description; + descDiv.style.cssText = ` + font-size: 14px; + color: ${this.options?.darkMode ? '#9ca3af' : '#6b7280'}; + `; + + contentDiv.appendChild(titleDiv); + contentDiv.appendChild(descDiv); + + button.appendChild(iconDiv); + button.appendChild(contentDiv); + this.modalBody.appendChild(button); + }); + } + + _handleOptionClick(type) { + console.log('Selected login type:', type); + + // Handle different login types + switch (type) { + case 'extension': + this._handleExtension(); + break; + case 'local': + this._showLocalKeyScreen(); + break; + case 'connect': + this._showConnectScreen(); + break; + case 'readonly': + this._handleReadonly(); + break; + case 'otp': + this._showOtpScreen(); + break; + } + } + + _handleExtension() { + // Detect all available real extensions + const availableExtensions = this._detectAllExtensions(); + + if (availableExtensions.length === 0) { + console.log('Modal: No real extensions found'); + this._showExtensionRequired(); + } else if (availableExtensions.length === 1) { + console.log('Modal detected single extension:', availableExtensions[0].name); + this._tryExtensionLogin(availableExtensions[0].extension); + } else { + console.log('Modal detected multiple extensions:', availableExtensions.map(e => e.name)); + this._showExtensionChoice(availableExtensions); + } + } + + _detectAllExtensions() { + const extensions = []; + + // Check navigator.nostr (NIP-07 standard location) + if (window.navigator?.nostr && typeof window.navigator.nostr.getPublicKey === 'function') { + extensions.push({ + name: 'navigator.nostr', + displayName: 'Standard Extension (navigator.nostr)', + icon: '🌐', + extension: window.navigator.nostr + }); + } + + // Check webln.nostr (Alby WebLN) + if (window.webln?.nostr && typeof window.webln.nostr.getPublicKey === 'function') { + extensions.push({ + name: 'webln.nostr', + displayName: 'Alby WebLN Extension', + icon: '⚡', + extension: window.webln.nostr + }); + } + + // Check alby.nostr (Alby direct) + if (window.alby?.nostr && typeof window.alby.nostr.getPublicKey === 'function') { + extensions.push({ + name: 'alby.nostr', + displayName: 'Alby Extension (Direct)', + icon: '🐝', + extension: window.alby.nostr + }); + } + + // Check nos2x + if (window.nos2x && typeof window.nos2x.getPublicKey === 'function') { + extensions.push({ + name: 'nos2x', + displayName: 'nos2x Extension', + icon: '🔌', + extension: window.nos2x + }); + } + + // Check window.nostr but make sure it's not our library + if (window.nostr && typeof window.nostr.getPublicKey === 'function') { + const isRealExtension = ( + typeof window.nostr._hexToUint8Array !== 'function' && // Our library has this method + window.nostr.constructor.name !== 'Object' // Real extensions usually have proper constructors + ); + + if (isRealExtension) { + // Don't add if we already detected it via another path + const alreadyDetected = extensions.some(ext => ext.extension === window.nostr); + if (!alreadyDetected) { + extensions.push({ + name: 'window.nostr', + displayName: 'Extension (window.nostr)', + icon: '🔑', + extension: window.nostr + }); + } + } + } + + return extensions; + } + + _showExtensionChoice(extensions) { + this.modalBody.innerHTML = ''; + + const title = document.createElement('h3'); + title.textContent = 'Choose Browser Extension'; + title.style.cssText = 'margin: 0 0 16px 0; font-size: 18px; font-weight: 600;'; + + const description = document.createElement('p'); + description.textContent = `Found ${extensions.length} Nostr extensions. Choose which one to use:`; + description.style.cssText = 'margin-bottom: 20px; color: #6b7280; font-size: 14px;'; + + this.modalBody.appendChild(title); + this.modalBody.appendChild(description); + + // Create button for each extension + extensions.forEach((ext, index) => { + const button = document.createElement('button'); + button.onclick = () => this._tryExtensionLogin(ext.extension); + button.style.cssText = ` + display: flex; + align-items: center; + width: 100%; + padding: 16px; + margin-bottom: 12px; + background: ${this.options?.darkMode ? '#374151' : 'white'}; + border: 1px solid ${this.options?.darkMode ? '#4b5563' : '#d1d5db'}; + border-radius: 8px; + cursor: pointer; + transition: all 0.2s; + text-align: left; + `; + + button.onmouseover = () => { + button.style.boxShadow = '0 4px 6px -1px rgba(0, 0, 0, 0.1)'; + button.style.transform = 'translateY(-1px)'; + }; + button.onmouseout = () => { + button.style.boxShadow = 'none'; + button.style.transform = 'none'; + }; + + const iconDiv = document.createElement('div'); + iconDiv.textContent = ext.icon; + iconDiv.style.cssText = ` + font-size: 24px; + margin-right: 16px; + width: 24px; + text-align: center; + `; + + const contentDiv = document.createElement('div'); + contentDiv.style.cssText = 'flex: 1;'; + + const nameDiv = document.createElement('div'); + nameDiv.textContent = ext.displayName; + nameDiv.style.cssText = ` + font-weight: 600; + margin-bottom: 4px; + color: ${this.options?.darkMode ? 'white' : '#1f2937'}; + `; + + const pathDiv = document.createElement('div'); + pathDiv.textContent = ext.name; + pathDiv.style.cssText = ` + font-size: 12px; + color: ${this.options?.darkMode ? '#9ca3af' : '#6b7280'}; + font-family: monospace; + `; + + contentDiv.appendChild(nameDiv); + contentDiv.appendChild(pathDiv); + + button.appendChild(iconDiv); + button.appendChild(contentDiv); + this.modalBody.appendChild(button); + }); + + // Add back button + const backButton = document.createElement('button'); + backButton.textContent = 'Back to Login Options'; + backButton.onclick = () => this._renderLoginOptions(); + backButton.style.cssText = this._getButtonStyle('secondary') + 'margin-top: 20px;'; + + this.modalBody.appendChild(backButton); + } + + async _tryExtensionLogin(extensionObj) { + try { + // Show loading state + this.modalBody.innerHTML = '
🔄 Connecting to extension...
'; + + // Get pubkey from extension + const pubkey = await extensionObj.getPublicKey(); + console.log('Extension provided pubkey:', pubkey); + + // Set extension method with the extension object + this._setAuthMethod('extension', { pubkey, extension: extensionObj }); + + } catch (error) { + console.error('Extension login failed:', error); + this._showError(`Extension login failed: ${error.message}`); + } + } + + _showLocalKeyScreen() { + this.modalBody.innerHTML = ''; + + const title = document.createElement('h3'); + title.textContent = 'Local Key'; + title.style.cssText = 'margin: 0 0 20px 0; font-size: 18px; font-weight: 600;'; + + const createButton = document.createElement('button'); + createButton.textContent = 'Create New Key'; + createButton.onclick = () => this._createLocalKey(); + createButton.style.cssText = this._getButtonStyle(); + + const importButton = document.createElement('button'); + importButton.textContent = 'Import Existing Key'; + importButton.onclick = () => this._showImportKeyForm(); + importButton.style.cssText = this._getButtonStyle() + 'margin-top: 12px;'; + + const backButton = document.createElement('button'); + backButton.textContent = 'Back'; + backButton.onclick = () => this._renderLoginOptions(); + backButton.style.cssText = ` + display: block; + margin-top: 20px; + padding: 12px; + background: #6b7280; + color: white; + border: none; + border-radius: 6px; + cursor: pointer; + `; + + this.modalBody.appendChild(title); + this.modalBody.appendChild(createButton); + this.modalBody.appendChild(importButton); + this.modalBody.appendChild(backButton); + } + + _createLocalKey() { + try { + const sk = window.NostrTools.generateSecretKey(); + const pk = window.NostrTools.getPublicKey(sk); + const nsec = window.NostrTools.nip19.nsecEncode(sk); + const npub = window.NostrTools.nip19.npubEncode(pk); + + this._showKeyDisplay(pk, nsec, 'created'); + } catch (error) { + this._showError('Failed to create key: ' + error.message); + } + } + + _showImportKeyForm() { + this.modalBody.innerHTML = ''; + + const title = document.createElement('h3'); + title.textContent = 'Import Local Key'; + title.style.cssText = 'margin: 0 0 16px 0; font-size: 18px; font-weight: 600;'; + + const description = document.createElement('p'); + description.textContent = 'Enter your secret key in either nsec or hex format:'; + description.style.cssText = 'margin-bottom: 12px; color: #6b7280; font-size: 14px;'; + + const textarea = document.createElement('textarea'); + textarea.placeholder = 'Enter your secret key:\n• nsec1... (bech32 format)\n• 64-character hex string'; + textarea.style.cssText = ` + width: 100%; + height: 100px; + padding: 12px; + border: 1px solid #d1d5db; + border-radius: 6px; + margin-bottom: 12px; + resize: none; + font-family: monospace; + font-size: 14px; + box-sizing: border-box; + `; + + // Add real-time format detection + const formatHint = document.createElement('div'); + formatHint.style.cssText = 'margin-bottom: 16px; font-size: 12px; color: #6b7280; min-height: 16px;'; + + textarea.oninput = () => { + const value = textarea.value.trim(); + if (!value) { + formatHint.textContent = ''; + return; + } + + const format = this._detectKeyFormat(value); + if (format === 'nsec') { + formatHint.textContent = '✅ Valid nsec format detected'; + formatHint.style.color = '#059669'; + } else if (format === 'hex') { + formatHint.textContent = '✅ Valid hex format detected'; + formatHint.style.color = '#059669'; + } else { + formatHint.textContent = '❌ Invalid key format - must be nsec1... or 64-character hex'; + formatHint.style.color = '#dc2626'; + } + }; + + const importButton = document.createElement('button'); + importButton.textContent = 'Import Key'; + importButton.onclick = () => this._importLocalKey(textarea.value); + importButton.style.cssText = this._getButtonStyle(); + + const backButton = document.createElement('button'); + backButton.textContent = 'Back'; + backButton.onclick = () => this._showLocalKeyScreen(); + backButton.style.cssText = this._getButtonStyle('secondary') + 'margin-top: 12px;'; + + this.modalBody.appendChild(title); + this.modalBody.appendChild(description); + this.modalBody.appendChild(textarea); + this.modalBody.appendChild(formatHint); + this.modalBody.appendChild(importButton); + this.modalBody.appendChild(backButton); + } + + _detectKeyFormat(keyValue) { + const trimmed = keyValue.trim(); + + // Check for nsec format + if (trimmed.startsWith('nsec1') && trimmed.length === 63) { + try { + window.NostrTools.nip19.decode(trimmed); + return 'nsec'; + } catch { + return 'invalid'; + } + } + + // Check for hex format (64 characters, valid hex) + if (trimmed.length === 64 && /^[a-fA-F0-9]{64}$/.test(trimmed)) { + return 'hex'; + } + + return 'invalid'; + } + + _importLocalKey(keyValue) { + try { + const trimmed = keyValue.trim(); + if (!trimmed) { + throw new Error('Please enter a secret key'); + } + + const format = this._detectKeyFormat(trimmed); + let sk; + + if (format === 'nsec') { + // Decode nsec format - this returns Uint8Array + const decoded = window.NostrTools.nip19.decode(trimmed); + if (decoded.type !== 'nsec') { + throw new Error('Invalid nsec format'); + } + sk = decoded.data; // This is already Uint8Array + } else if (format === 'hex') { + // Convert hex string to Uint8Array + sk = this._hexToUint8Array(trimmed); + // Test that it's a valid secret key by trying to get public key + window.NostrTools.getPublicKey(sk); + } else { + throw new Error('Invalid key format. Please enter either nsec1... or 64-character hex string'); + } + + // Generate public key and encoded formats + const pk = window.NostrTools.getPublicKey(sk); + const nsec = window.NostrTools.nip19.nsecEncode(sk); + const npub = window.NostrTools.nip19.npubEncode(pk); + + this._showKeyDisplay(pk, nsec, 'imported'); + } catch (error) { + this._showError('Invalid key: ' + error.message); + } + } + + _hexToUint8Array(hex) { + // Convert hex string to Uint8Array + if (hex.length % 2 !== 0) { + throw new Error('Invalid hex string length'); + } + const bytes = new Uint8Array(hex.length / 2); + for (let i = 0; i < bytes.length; i++) { + bytes[i] = parseInt(hex.substr(i * 2, 2), 16); + } + return bytes; + } + + _showKeyDisplay(pubkey, nsec, action) { + this.modalBody.innerHTML = ''; + + const title = document.createElement('h3'); + title.textContent = `Key ${action} successfully!`; + title.style.cssText = 'margin: 0 0 16px 0; font-size: 18px; font-weight: 600; color: #059669;'; + + const warningDiv = document.createElement('div'); + warningDiv.textContent = '⚠️ Save your secret key securely!'; + warningDiv.style.cssText = 'background: #fef3c7; color: #92400e; padding: 12px; border-radius: 6px; margin-bottom: 16px; font-size: 14px;'; + + const nsecDiv = document.createElement('div'); + nsecDiv.innerHTML = `Your Secret Key:
${nsec}`; + nsecDiv.style.cssText = 'margin-bottom: 16px; font-size: 14px;'; + + const npubDiv = document.createElement('div'); + npubDiv.innerHTML = `Your Public Key:
${window.NostrTools.nip19.npubEncode(pubkey)}`; + npubDiv.style.cssText = 'margin-bottom: 16px; font-size: 14px;'; + + const continueButton = document.createElement('button'); + continueButton.textContent = 'Continue'; + continueButton.onclick = () => this._setAuthMethod('local', { secret: nsec, pubkey }); + continueButton.style.cssText = this._getButtonStyle(); + + this.modalBody.appendChild(title); + this.modalBody.appendChild(warningDiv); + this.modalBody.appendChild(nsecDiv); + this.modalBody.appendChild(npubDiv); + this.modalBody.appendChild(continueButton); + } + + _setAuthMethod(method, options = {}) { + // Emit auth method selection + const event = new CustomEvent('nlMethodSelected', { + detail: { method, ...options } + }); + window.dispatchEvent(event); + + this.close(); + } + + _showError(message) { + this.modalBody.innerHTML = ''; + + const errorDiv = document.createElement('div'); + errorDiv.style.cssText = 'background: #fee2e2; color: #dc2626; padding: 16px; border-radius: 6px; margin-bottom: 16px;'; + errorDiv.innerHTML = `Error: ${message}`; + + const backButton = document.createElement('button'); + backButton.textContent = 'Back'; + backButton.onclick = () => this._renderLoginOptions(); + backButton.style.cssText = this._getButtonStyle('secondary'); + + this.modalBody.appendChild(errorDiv); + this.modalBody.appendChild(backButton); + } + + _showExtensionRequired() { + this.modalBody.innerHTML = ''; + + const title = document.createElement('h3'); + title.textContent = 'Browser Extension Required'; + title.style.cssText = 'margin: 0 0 16px 0; font-size: 18px; font-weight: 600;'; + + const message = document.createElement('p'); + message.textContent = 'Please install a Nostr browser extension like Alby or getflattr and refresh the page.'; + message.style.cssText = 'margin-bottom: 20px; color: #6b7280;'; + + const backButton = document.createElement('button'); + backButton.textContent = 'Back'; + backButton.onclick = () => this._renderLoginOptions(); + backButton.style.cssText = this._getButtonStyle('secondary'); + + this.modalBody.appendChild(title); + this.modalBody.appendChild(message); + this.modalBody.appendChild(backButton); + } + + _showConnectScreen() { + this.modalBody.innerHTML = ''; + + const title = document.createElement('h3'); + title.textContent = 'Connect to NIP-46 Remote Signer'; + title.style.cssText = 'margin: 0 0 16px 0; font-size: 18px; font-weight: 600;'; + + const description = document.createElement('p'); + description.textContent = 'Connect to a remote signer (bunker) server to use its keys for signing.'; + description.style.cssText = 'margin-bottom: 20px; color: #6b7280; font-size: 14px;'; + + const formGroup = document.createElement('div'); + formGroup.style.cssText = 'margin-bottom: 20px;'; + + const label = document.createElement('label'); + label.textContent = 'Bunker Public Key:'; + label.style.cssText = 'display: block; margin-bottom: 8px; font-weight: 500;'; + + const pubkeyInput = document.createElement('input'); + pubkeyInput.type = 'text'; + pubkeyInput.placeholder = 'bunker://pubkey?relay=..., bunker:hex, hex, or npub...'; + pubkeyInput.style.cssText = ` + width: 100%; + padding: 12px; + border: 1px solid #d1d5db; + border-radius: 6px; + margin-bottom: 12px; + font-family: monospace; + box-sizing: border-box; + `; + + const urlLabel = document.createElement('label'); + urlLabel.textContent = 'Remote URL (optional):'; + urlLabel.style.cssText = 'display: block; margin-bottom: 8px; font-weight: 500;'; + + const urlInput = document.createElement('input'); + urlInput.type = 'url'; + urlInput.placeholder = 'ws://localhost:8080 (default)'; + urlInput.style.cssText = ` + width: 100%; + padding: 12px; + border: 1px solid #d1d5db; + border-radius: 6px; + margin-bottom: 16px; + box-sizing: border-box; + `; + + // Pre-fill with our bunker config if available + if (window.NIP46_BUNKER_CONFIG) { + pubkeyInput.value = window.NIP46_BUNKER_CONFIG.remoteSigner.pubkey; + urlInput.value = window.NIP46_BUNKER_CONFIG.remoteSigner.url; + } + + const connectButton = document.createElement('button'); + connectButton.textContent = 'Connect to Bunker'; + connectButton.onclick = () => this._handleNip46Connect(pubkeyInput.value, urlInput.value); + connectButton.style.cssText = this._getButtonStyle(); + + const backButton = document.createElement('button'); + backButton.textContent = 'Back'; + backButton.onclick = () => this._renderLoginOptions(); + backButton.style.cssText = this._getButtonStyle('secondary') + 'margin-top: 12px;'; + + formGroup.appendChild(label); + formGroup.appendChild(pubkeyInput); + formGroup.appendChild(urlLabel); + formGroup.appendChild(urlInput); + + this.modalBody.appendChild(title); + this.modalBody.appendChild(description); + this.modalBody.appendChild(formGroup); + this.modalBody.appendChild(connectButton); + this.modalBody.appendChild(backButton); + } + + _handleNip46Connect(bunkerPubkey, bunkerUrl) { + if (!bunkerPubkey || !bunkerPubkey.length) { + this._showError('Bunker pubkey is required'); + return; + } + + this._showNip46Connecting(bunkerPubkey, bunkerUrl); + this._performNip46Connect(bunkerPubkey, bunkerUrl); + } + + _showNip46Connecting(bunkerPubkey, bunkerUrl) { + this.modalBody.innerHTML = ''; + + const title = document.createElement('h3'); + title.textContent = 'Connecting to Remote Signer...'; + title.style.cssText = 'margin: 0 0 16px 0; font-size: 18px; font-weight: 600; color: #059669;'; + + const description = document.createElement('p'); + description.textContent = 'Establishing secure connection to your remote signer.'; + description.style.cssText = 'margin-bottom: 20px; color: #6b7280;'; + + // Normalize bunker pubkey for display (= show original format if bunker: prefix) + const displayPubkey = bunkerPubkey.startsWith('bunker:') || bunkerPubkey.startsWith('npub') || bunkerPubkey.length === 64 ? bunkerPubkey : bunkerPubkey; + + const bunkerInfo = document.createElement('div'); + bunkerInfo.style.cssText = 'background: #f1f5f9; padding: 12px; border-radius: 6px; margin-bottom: 20px; font-size: 14px;'; + bunkerInfo.innerHTML = ` + Connecting to bunker:
+ Pubkey: ${displayPubkey}
+ Relay: ${bunkerUrl || 'ws://localhost:8080'}
+ If this relay is offline, the bunker server may be unavailable. + `; + + const connectingDiv = document.createElement('div'); + connectingDiv.style.cssText = 'text-align: center; color: #6b7280;'; + connectingDiv.innerHTML = ` +
+
Please wait while we establish the connection...
+
This may take a few seconds
+ `; + + this.modalBody.appendChild(title); + this.modalBody.appendChild(description); + this.modalBody.appendChild(bunkerInfo); + this.modalBody.appendChild(connectingDiv); + } + + async _performNip46Connect(bunkerPubkey, bunkerUrl) { + try { + console.log('Starting NIP-46 connection to bunker:', bunkerPubkey, bunkerUrl); + + // Check if nostr-tools NIP-46 is available + if (!window.NostrTools?.nip46) { + throw new Error('nostr-tools NIP-46 module not available'); + } + + // Use nostr-tools to parse bunker input - this handles all formats correctly + console.log('Parsing bunker input with nostr-tools...'); + const bunkerPointer = await window.NostrTools.nip46.parseBunkerInput(bunkerPubkey); + + if (!bunkerPointer) { + throw new Error('Unable to parse bunker connection string or resolve NIP-05 identifier'); + } + + console.log('Parsed bunker pointer:', bunkerPointer); + + // Create local client keypair for this session + const localSecretKey = window.NostrTools.generateSecretKey(); + console.log('Generated local client keypair for NIP-46 session'); + + // Use nostr-tools BunkerSigner.fromBunker() for bunker:// connections + console.log('Creating nip46 BunkerSigner...'); + const signer = window.NostrTools.nip46.BunkerSigner.fromBunker(localSecretKey, bunkerPointer, { + onauth: (url) => { + console.log('Received auth URL from bunker:', url); + // Open auth URL in popup or redirect + window.open(url, '_blank', 'width=600,height=800'); + } + }); + + console.log('NIP-46 BunkerSigner created successfully'); + + // Attempt initial ping to verify connection + console.log('Testing bunker connection with ping...'); + await signer.ping(); + console.log('NIP-46 ping successful - bunker is reachable'); + + // Try to connect (this may trigger auth flow) + console.log('Attempting NIP-46 connect...'); + await signer.connect(); + console.log('NIP-46 connect successful'); + + // Get the user's public key from the bunker + console.log('Getting public key from bunker...'); + const userPubkey = await signer.getPublicKey(); + console.log('NIP-46 user public key:', userPubkey); + + // Store the NIP-46 authentication info + const nip46Info = { + pubkey: userPubkey, + signer: { + method: 'nip46', + remotePubkey: bunkerPointer.pubkey, + bunkerSigner: signer, + secret: bunkerPointer.secret, + relays: bunkerPointer.relays + } + }; + + console.log('NOSTR_LOGIN_LITE NIP-46 connection established successfully!'); + + // Set as current auth method + this._setAuthMethod('nip46', nip46Info); + return; + + } catch (error) { + console.error('NIP-46 connection failed:', error); + this._showNip46Error(error.message); + } + } + + _showNip46Error(message) { + this.modalBody.innerHTML = ''; + + const title = document.createElement('h3'); + title.textContent = 'Connection Failed'; + title.style.cssText = 'margin: 0 0 16px 0; font-size: 18px; font-weight: 600; color: #dc2626;'; + + const errorMsg = document.createElement('p'); + errorMsg.textContent = `Unable to connect to remote signer: ${message}`; + errorMsg.style.cssText = 'margin-bottom: 20px; color: #6b7280;'; + + const retryButton = document.createElement('button'); + retryButton.textContent = 'Try Again'; + retryButton.onclick = () => this._showConnectScreen(); + retryButton.style.cssText = this._getButtonStyle(); + + const backButton = document.createElement('button'); + backButton.textContent = 'Back to Options'; + backButton.onclick = () => this._renderLoginOptions(); + backButton.style.cssText = this._getButtonStyle('secondary') + 'margin-top: 12px;'; + + this.modalBody.appendChild(title); + this.modalBody.appendChild(errorMsg); + this.modalBody.appendChild(retryButton); + this.modalBody.appendChild(backButton); + } + + _handleReadonly() { + // Set read-only mode + this._setAuthMethod('readonly'); + } + + _showOtpScreen() { + // Placeholder for OTP functionality + this._showError('OTP/DM not yet implemented - coming soon!'); + } + + _getButtonStyle(type = 'primary') { + const baseStyle = ` + display: block; + width: 100%; + padding: 12px; + border: none; + border-radius: 8px; + font-size: 16px; + font-weight: 500; + cursor: pointer; + transition: all 0.2s; + `; + + if (type === 'primary') { + return baseStyle + ` + background: #3b82f6; + color: white; + `; + } else { + return baseStyle + ` + background: #6b7280; + color: white; + `; + } + } + + // Public API + static init(options) { + if (Modal.instance) return Modal.instance; + Modal.instance = new Modal(options); + return Modal.instance; + } + + static getInstance() { + return Modal.instance; + } +} + +// Initialize global instance +let modalInstance = null; + +window.addEventListener('load', () => { + modalInstance = new Modal(); +}); \ No newline at end of file