diff --git a/api/nostr-lite.js b/api/nostr-lite.js index 87a81ee..74ac713 100644 --- a/api/nostr-lite.js +++ b/api/nostr-lite.js @@ -8,7 +8,7 @@ * Two-file architecture: * 1. Load nostr.bundle.js (official nostr-tools bundle) * 2. Load nostr-lite.js (this file - NOSTR_LOGIN_LITE library with CSS-only themes) - * Generated on: 2025-09-16T15:52:30.145Z + * Generated on: 2025-09-16T22:12:00.192Z */ // Verify dependencies are loaded @@ -20,509 +20,10 @@ if (typeof window !== 'undefined') { console.log('NOSTR_LOGIN_LITE: Dependencies verified ✓'); console.log('NOSTR_LOGIN_LITE: NostrTools available with keys:', Object.keys(window.NostrTools)); + console.log('NOSTR_LOGIN_LITE: NIP-06 available:', !!window.NostrTools.nip06); console.log('NOSTR_LOGIN_LITE: NIP-46 available:', !!window.NostrTools.nip46); } -// ===== NIP-46 Extension Integration ===== -// Add NIP-46 functionality to NostrTools if not already present -if (typeof window.NostrTools !== 'undefined' && !window.NostrTools.nip46) { - console.log('NOSTR_LOGIN_LITE: Adding NIP-46 extension to NostrTools'); - - const { nip44, generateSecretKey, getPublicKey, finalizeEvent, verifyEvent, utils } = window.NostrTools; - - // NIP-05 regex for parsing - const NIP05_REGEX = /^(?:([\w.+-]+)@)?([\w_-]+(.[\w_-]+)+)$/; - const BUNKER_REGEX = /^bunker:\/\/([0-9a-f]{64})\??([?\/\w:.=&%-]*)$/; - const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; - - // Event kinds - const NostrConnect = 24133; - const ClientAuth = 22242; - const Handlerinformation = 31990; - - // Fetch implementation - let _fetch; - try { - _fetch = fetch; - } catch { - _fetch = null; - } - - function useFetchImplementation(fetchImplementation) { - _fetch = fetchImplementation; - } - - // Simple Pool implementation for NIP-46 - class SimplePool { - constructor() { - this.relays = new Map(); - this.subscriptions = new Map(); - } - - async ensureRelay(url) { - if (!this.relays.has(url)) { - console.log(`NIP-46: Connecting to relay ${url}`); - const ws = new WebSocket(url); - const relay = { - ws, - connected: false, - subscriptions: new Map() - }; - - this.relays.set(url, relay); - - // Wait for connection with proper event handlers - await new Promise((resolve, reject) => { - const timeout = setTimeout(() => { - console.error(`NIP-46: Connection timeout for ${url}`); - reject(new Error(`Connection timeout to ${url}`)); - }, 10000); // 10 second timeout - - ws.onopen = () => { - console.log(`NIP-46: Successfully connected to relay ${url}, WebSocket state: ${ws.readyState}`); - relay.connected = true; - clearTimeout(timeout); - resolve(); - }; - - ws.onerror = (error) => { - console.error(`NIP-46: Failed to connect to ${url}:`, error); - clearTimeout(timeout); - reject(new Error(`Failed to connect to ${url}: ${error.message || 'Connection failed'}`)); - }; - - ws.onclose = (event) => { - console.log(`NIP-46: Disconnected from relay ${url}:`, event.code, event.reason); - relay.connected = false; - if (this.relays.has(url)) { - this.relays.delete(url); - } - clearTimeout(timeout); - reject(new Error(`Connection closed during setup: ${event.reason || 'Unknown reason'}`)); - }; - }); - } else { - const relay = this.relays.get(url); - // Verify the existing connection is still open - if (!relay.connected || relay.ws.readyState !== WebSocket.OPEN) { - console.log(`NIP-46: Reconnecting to relay ${url}`); - this.relays.delete(url); - return await this.ensureRelay(url); // Recursively reconnect - } - } - - const relay = this.relays.get(url); - console.log(`NIP-46: Relay ${url} ready, WebSocket state: ${relay.ws.readyState}`); - return relay; - } - - subscribe(relays, filters, params = {}) { - const subId = Math.random().toString(36).substring(7); - - relays.forEach(async (url) => { - try { - const relay = await this.ensureRelay(url); - - relay.ws.onmessage = (event) => { - try { - const data = JSON.parse(event.data); - if (data[0] === 'EVENT' && data[1] === subId) { - params.onevent?.(data[2]); - } else if (data[0] === 'EOSE' && data[1] === subId) { - params.oneose?.(); - } - } catch (err) { - console.warn('Failed to parse message:', err); - } - }; - - // Ensure filters is an array - const filtersArray = Array.isArray(filters) ? filters : [filters]; - const reqMsg = JSON.stringify(['REQ', subId, ...filtersArray]); - relay.ws.send(reqMsg); - - } catch (err) { - console.warn('Failed to connect to relay:', url, err); - } - }); - - return { - close: () => { - relays.forEach(async (url) => { - const relay = this.relays.get(url); - if (relay?.connected) { - relay.ws.send(JSON.stringify(['CLOSE', subId])); - } - }); - } - }; - } - - async publish(relays, event) { - console.log(`NIP-46: Publishing event to ${relays.length} relays:`, event); - - const promises = relays.map(async (url) => { - try { - console.log(`NIP-46: Attempting to publish to ${url}`); - const relay = await this.ensureRelay(url); - - return new Promise((resolve, reject) => { - const timeout = setTimeout(() => { - console.error(`NIP-46: Publish timeout to ${url}`); - reject(new Error(`Publish timeout to ${url}`)); - }, 10000); // Increased timeout to 10 seconds - - // Set up message handler for this specific event - const messageHandler = (msg) => { - try { - const data = JSON.parse(msg.data); - if (data[0] === 'OK' && data[1] === event.id) { - clearTimeout(timeout); - relay.ws.removeEventListener('message', messageHandler); - if (data[2]) { - console.log(`NIP-46: Publish success to ${url}:`, data[3]); - resolve(data[3]); - } else { - console.error(`NIP-46: Publish rejected by ${url}:`, data[3]); - reject(new Error(`Publish rejected: ${data[3]}`)); - } - } - } catch (err) { - console.error(`NIP-46: Error parsing message from ${url}:`, err); - clearTimeout(timeout); - relay.ws.removeEventListener('message', messageHandler); - reject(err); - } - }; - - relay.ws.addEventListener('message', messageHandler); - - // Double-check WebSocket state before sending - console.log(`NIP-46: About to publish to ${url}, WebSocket state: ${relay.ws.readyState} (0=CONNECTING, 1=OPEN, 2=CLOSING, 3=CLOSED)`); - if (relay.ws.readyState === WebSocket.OPEN) { - console.log(`NIP-46: Sending event to ${url}`); - relay.ws.send(JSON.stringify(['EVENT', event])); - } else { - console.error(`NIP-46: WebSocket not ready for ${url}, state: ${relay.ws.readyState}`); - clearTimeout(timeout); - relay.ws.removeEventListener('message', messageHandler); - reject(new Error(`WebSocket not ready for ${url}, state: ${relay.ws.readyState}`)); - } - }); - } catch (err) { - console.error(`NIP-46: Failed to publish to ${url}:`, err); - return Promise.reject(new Error(`Failed to publish to ${url}: ${err.message}`)); - } - }); - - const results = await Promise.allSettled(promises); - console.log(`NIP-46: Publish results:`, results); - return results; - } - - async querySync(relays, filter, params = {}) { - return new Promise((resolve) => { - const events = []; - this.subscribe(relays, [filter], { - ...params, - onevent: (event) => events.push(event), - oneose: () => resolve(events) - }); - }); - } - } - - // Bunker URL utilities - function toBunkerURL(bunkerPointer) { - let bunkerURL = new URL(`bunker://${bunkerPointer.pubkey}`); - bunkerPointer.relays.forEach((relay) => { - bunkerURL.searchParams.append('relay', relay); - }); - if (bunkerPointer.secret) { - bunkerURL.searchParams.set('secret', bunkerPointer.secret); - } - return bunkerURL.toString(); - } - - async function parseBunkerInput(input) { - let match = input.match(BUNKER_REGEX); - if (match) { - try { - const pubkey = match[1]; - const qs = new URLSearchParams(match[2]); - return { - pubkey, - relays: qs.getAll('relay'), - secret: qs.get('secret') - }; - } catch (_err) { - // Continue to NIP-05 parsing - } - } - return queryBunkerProfile(input); - } - - async function queryBunkerProfile(nip05) { - if (!_fetch) { - throw new Error('Fetch implementation not available'); - } - - const match = nip05.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 (await _fetch(url, { redirect: 'error' })).json(); - let pubkey = res.names[name]; - let relays = res.nip46[pubkey] || []; - return { pubkey, relays, secret: null }; - } catch (_err) { - return null; - } - } - - // BunkerSigner class - class BunkerSigner { - constructor(clientSecretKey, bp, params = {}) { - if (bp.relays.length === 0) { - throw new Error('no relays are specified for this bunker'); - } - - this.params = params; - this.pool = params.pool || new SimplePool(); - this.secretKey = clientSecretKey; - this.conversationKey = nip44.getConversationKey(clientSecretKey, bp.pubkey); - this.bp = bp; - this.isOpen = false; - this.idPrefix = Math.random().toString(36).substring(7); - this.serial = 0; - this.listeners = {}; - this.waitingForAuth = {}; - this.ready = false; - this.readyPromise = this.setupSubscription(params); - } - - async setupSubscription(params) { - console.log('NIP-46: Setting up subscription to relays:', this.bp.relays); - const listeners = this.listeners; - const waitingForAuth = this.waitingForAuth; - const convKey = this.conversationKey; - - // Ensure all relays are connected first - await Promise.all(this.bp.relays.map(url => this.pool.ensureRelay(url))); - console.log('NIP-46: All relays connected, setting up subscription'); - - this.subCloser = this.pool.subscribe( - this.bp.relays, - [{ kinds: [NostrConnect], authors: [this.bp.pubkey], '#p': [getPublicKey(this.secretKey)] }], - { - onevent: async (event) => { - const o = JSON.parse(nip44.decrypt(event.content, convKey)); - const { id, result, error } = o; - - if (result === 'auth_url' && waitingForAuth[id]) { - delete waitingForAuth[id]; - if (params.onauth) { - params.onauth(error); - } else { - console.warn( - `NIP-46: remote signer ${this.bp.pubkey} tried to send an "auth_url"='${error}' but there was no onauth() callback configured.` - ); - } - return; - } - - let handler = listeners[id]; - if (handler) { - if (error) handler.reject(error); - else if (result) handler.resolve(result); - delete listeners[id]; - } - }, - onclose: () => { - this.subCloser = undefined; - } - } - ); - - this.isOpen = true; - this.ready = true; - console.log('NIP-46: BunkerSigner setup complete and ready'); - } - - async ensureReady() { - if (!this.ready) { - console.log('NIP-46: Waiting for BunkerSigner to be ready...'); - await this.readyPromise; - } - } - - async close() { - this.isOpen = false; - this.subCloser?.close(); - } - - async sendRequest(method, params) { - return new Promise(async (resolve, reject) => { - try { - await this.ensureReady(); // Wait for BunkerSigner to be ready - - if (!this.isOpen) { - throw new Error('this signer is not open anymore, create a new one'); - } - if (!this.subCloser) { - await this.setupSubscription(this.params); - } - - this.serial++; - const id = `${this.idPrefix}-${this.serial}`; - const encryptedContent = nip44.encrypt(JSON.stringify({ id, method, params }), this.conversationKey); - - const verifiedEvent = finalizeEvent( - { - kind: NostrConnect, - tags: [['p', this.bp.pubkey]], - content: encryptedContent, - created_at: Math.floor(Date.now() / 1000) - }, - this.secretKey - ); - - this.listeners[id] = { resolve, reject }; - this.waitingForAuth[id] = true; - - console.log(`NIP-46: Sending ${method} request with id ${id}`); - const publishResults = await this.pool.publish(this.bp.relays, verifiedEvent); - // Check if at least one publish succeeded - const hasSuccess = publishResults.some(result => result.status === 'fulfilled'); - if (!hasSuccess) { - throw new Error('Failed to publish to any relay'); - } - console.log(`NIP-46: ${method} request sent successfully`); - } catch (err) { - console.error(`NIP-46: sendRequest ${method} failed:`, err); - reject(err); - } - }); - } - - async ping() { - let resp = await this.sendRequest('ping', []); - if (resp !== 'pong') { - throw new Error(`result is not pong: ${resp}`); - } - } - - async connect() { - await this.sendRequest('connect', [this.bp.pubkey, this.bp.secret || '']); - } - - async getPublicKey() { - if (!this.cachedPubKey) { - this.cachedPubKey = await this.sendRequest('get_public_key', []); - } - return this.cachedPubKey; - } - - async signEvent(event) { - let resp = await this.sendRequest('sign_event', [JSON.stringify(event)]); - let signed = JSON.parse(resp); - if (verifyEvent(signed)) { - return signed; - } else { - throw new Error(`event returned from bunker is improperly signed: ${JSON.stringify(signed)}`); - } - } - - async nip04Encrypt(thirdPartyPubkey, plaintext) { - return await this.sendRequest('nip04_encrypt', [thirdPartyPubkey, plaintext]); - } - - async nip04Decrypt(thirdPartyPubkey, ciphertext) { - return await this.sendRequest('nip04_decrypt', [thirdPartyPubkey, ciphertext]); - } - - async nip44Encrypt(thirdPartyPubkey, plaintext) { - return await this.sendRequest('nip44_encrypt', [thirdPartyPubkey, plaintext]); - } - - async nip44Decrypt(thirdPartyPubkey, ciphertext) { - return await this.sendRequest('nip44_decrypt', [thirdPartyPubkey, ciphertext]); - } - } - - async function createAccount(bunker, params, username, domain, email, localSecretKey = generateSecretKey()) { - if (email && !EMAIL_REGEX.test(email)) { - throw new Error('Invalid email'); - } - - let rpc = new BunkerSigner(localSecretKey, bunker.bunkerPointer, params); - let pubkey = await rpc.sendRequest('create_account', [username, domain, email || '']); - rpc.bp.pubkey = pubkey; - await rpc.connect(); - return rpc; - } - - async function fetchBunkerProviders(pool, relays) { - const events = await pool.querySync(relays, { - kinds: [Handlerinformation], - '#k': [NostrConnect.toString()] - }); - - events.sort((a, b) => b.created_at - a.created_at); - - const validatedBunkers = await Promise.all( - events.map(async (event, i) => { - try { - const content = JSON.parse(event.content); - try { - if (events.findIndex((ev) => JSON.parse(ev.content).nip05 === content.nip05) !== i) { - return undefined; - } - } catch (err) { - // Continue processing - } - - const bp = await queryBunkerProfile(content.nip05); - if (bp && bp.pubkey === event.pubkey && bp.relays.length) { - return { - bunkerPointer: bp, - nip05: content.nip05, - domain: content.nip05.split('@')[1], - name: content.name || content.display_name, - picture: content.picture, - about: content.about, - website: content.website, - local: false - }; - } - } catch (err) { - return undefined; - } - }) - ); - - return validatedBunkers.filter((b) => b !== undefined); - } - - // Extend NostrTools with NIP-46 functionality - window.NostrTools.nip46 = { - BunkerSigner, - parseBunkerInput, - toBunkerURL, - queryBunkerProfile, - createAccount, - fetchBunkerProviders, - useFetchImplementation, - BUNKER_REGEX, - SimplePool - }; - - console.log('NIP-46 extension loaded successfully'); - console.log('Available: NostrTools.nip46'); -} - // ====================================== // NOSTR_LOGIN_LITE Components // ====================================== @@ -854,7 +355,7 @@ class Modal { overflow: hidden; `; } else { - // Modal content: centered with margin + // Modal content: centered with margin, no fixed height modalContent.style.cssText = ` position: relative; background: var(--nl-secondary-color); @@ -864,7 +365,6 @@ class Modal { margin: 50px auto; border-radius: var(--nl-border-radius, 15px); border: var(--nl-border-width) solid var(--nl-primary-color); - max-height: 600px; overflow: hidden; `; } @@ -929,8 +429,6 @@ class Modal { this.modalBody = document.createElement('div'); this.modalBody.style.cssText = ` padding: 24px; - overflow-y: auto; - max-height: 500px; background: transparent; font-family: var(--nl-font-family, 'Courier New', monospace); `; @@ -1019,6 +517,16 @@ class Modal { }); } + // Seed Phrase option - only show if explicitly enabled + if (this.options?.methods?.seedphrase === true) { + options.push({ + type: 'seedphrase', + title: 'Seed Phrase', + description: 'Import from mnemonic seed phrase', + icon: '🌱' + }); + } + // Nostr Connect option (check both 'connect' and 'remote' for compatibility) if (this.options?.methods?.connect !== false && this.options?.methods?.remote !== false) { options.push({ @@ -1076,6 +584,27 @@ class Modal { button.style.background = 'var(--nl-secondary-color)'; }; + const iconDiv = document.createElement('div'); + // Replace emoji icons with text-based ones + const iconMap = { + '🔌': '[EXT]', + '🔑': '[KEY]', + '🌱': '[SEED]', + '🌐': '[NET]', + '👁️': '[VIEW]', + '📱': '[SMS]' + }; + iconDiv.textContent = iconMap[option.icon] || option.icon; + iconDiv.style.cssText = ` + font-size: 16px; + font-weight: bold; + margin-right: 16px; + width: 50px; + text-align: center; + color: var(--nl-primary-color); + font-family: var(--nl-font-family, 'Courier New', monospace); + `; + const contentDiv = document.createElement('div'); contentDiv.style.cssText = 'flex: 1; text-align: left;'; @@ -1099,6 +628,7 @@ class Modal { contentDiv.appendChild(titleDiv); contentDiv.appendChild(descDiv); + button.appendChild(iconDiv); button.appendChild(contentDiv); this.modalBody.appendChild(button); }); @@ -1115,6 +645,9 @@ class Modal { case 'local': this._showLocalKeyScreen(); break; + case 'seedphrase': + this._showSeedPhraseScreen(); + break; case 'connect': this._showConnectScreen(); break; @@ -2159,6 +1692,287 @@ class Modal { this._setAuthMethod('readonly'); } + _showSeedPhraseScreen() { + this.modalBody.innerHTML = ''; + + const title = document.createElement('h3'); + title.textContent = 'Import from Seed Phrase'; + title.style.cssText = 'margin: 0 0 16px 0; font-size: 18px; font-weight: 600;'; + + const description = document.createElement('p'); + description.textContent = 'Enter your 12 or 24-word mnemonic seed phrase to derive Nostr accounts:'; + description.style.cssText = 'margin-bottom: 12px; color: #6b7280; font-size: 14px;'; + + const textarea = document.createElement('textarea'); + // Remove default placeholder text as requested + textarea.placeholder = ''; + 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 mnemonic validation + 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 isValid = this._validateMnemonic(value); + if (isValid) { + const wordCount = value.split(/\s+/).length; + formatHint.textContent = `✅ Valid ${wordCount}-word mnemonic detected`; + formatHint.style.color = '#059669'; + } else { + formatHint.textContent = '❌ Invalid mnemonic - must be 12 or 24 valid BIP-39 words'; + formatHint.style.color = '#dc2626'; + } + }; + + // Generate new seed phrase button + const generateButton = document.createElement('button'); + generateButton.textContent = 'Generate New Seed Phrase'; + generateButton.onclick = () => this._generateNewSeedPhrase(textarea, formatHint); + generateButton.style.cssText = this._getButtonStyle() + 'margin-bottom: 12px;'; + + const importButton = document.createElement('button'); + importButton.textContent = 'Import Accounts'; + importButton.onclick = () => this._importFromSeedPhrase(textarea.value); + importButton.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;'; + + this.modalBody.appendChild(title); + this.modalBody.appendChild(description); + this.modalBody.appendChild(textarea); + this.modalBody.appendChild(formatHint); + this.modalBody.appendChild(generateButton); + this.modalBody.appendChild(importButton); + this.modalBody.appendChild(backButton); + } + + _generateNewSeedPhrase(textarea, formatHint) { + try { + // Check if NIP-06 is available + if (!window.NostrTools?.nip06) { + throw new Error('NIP-06 not available in bundle'); + } + + // Generate a random 12-word mnemonic using NostrTools + const mnemonic = window.NostrTools.nip06.generateSeedWords(); + + // Set the generated mnemonic in the textarea + textarea.value = mnemonic; + + // Trigger validation to show it's valid + const wordCount = mnemonic.split(/\s+/).length; + formatHint.textContent = `✅ Generated valid ${wordCount}-word mnemonic`; + formatHint.style.color = '#059669'; + + console.log('Generated new seed phrase:', wordCount, 'words'); + + } catch (error) { + console.error('Failed to generate seed phrase:', error); + formatHint.textContent = '❌ Failed to generate seed phrase - NIP-06 not available'; + formatHint.style.color = '#dc2626'; + } + } + + _validateMnemonic(mnemonic) { + try { + // Check if NIP-06 is available + if (!window.NostrTools?.nip06) { + console.error('NIP-06 not available in bundle'); + return false; + } + + const words = mnemonic.trim().split(/\s+/); + + // Must be 12 or 24 words + if (words.length !== 12 && words.length !== 24) { + return false; + } + + // Try to validate using NostrTools nip06 - this will throw if invalid + window.NostrTools.nip06.privateKeyFromSeedWords(mnemonic, '', 0); + return true; + } catch (error) { + console.log('Mnemonic validation failed:', error.message); + return false; + } + } + + _importFromSeedPhrase(mnemonic) { + try { + const trimmed = mnemonic.trim(); + if (!trimmed) { + throw new Error('Please enter a mnemonic seed phrase'); + } + + // Validate the mnemonic + if (!this._validateMnemonic(trimmed)) { + throw new Error('Invalid mnemonic. Please enter a valid 12 or 24-word BIP-39 seed phrase'); + } + + // Generate accounts 0-5 using NIP-06 + const accounts = []; + for (let i = 0; i < 6; i++) { + try { + const privateKey = window.NostrTools.nip06.privateKeyFromSeedWords(trimmed, '', i); + const publicKey = window.NostrTools.getPublicKey(privateKey); + const nsec = window.NostrTools.nip19.nsecEncode(privateKey); + const npub = window.NostrTools.nip19.npubEncode(publicKey); + + accounts.push({ + index: i, + privateKey, + publicKey, + nsec, + npub + }); + } catch (error) { + console.error(`Failed to derive account ${i}:`, error); + } + } + + if (accounts.length === 0) { + throw new Error('Failed to derive any accounts from seed phrase'); + } + + console.log(`Successfully derived ${accounts.length} accounts from seed phrase`); + this._showAccountSelection(accounts); + + } catch (error) { + console.error('Seed phrase import failed:', error); + this._showError('Seed phrase import failed: ' + error.message); + } + } + + _showAccountSelection(accounts) { + this.modalBody.innerHTML = ''; + + const title = document.createElement('h3'); + title.textContent = 'Select Account'; + title.style.cssText = 'margin: 0 0 16px 0; font-size: 18px; font-weight: 600;'; + + const description = document.createElement('p'); + description.textContent = `Select which account to use (${accounts.length} accounts derived from seed phrase):`; + description.style.cssText = 'margin-bottom: 20px; color: #6b7280; font-size: 14px;'; + + this.modalBody.appendChild(title); + this.modalBody.appendChild(description); + + // Create table for account selection + const table = document.createElement('table'); + table.style.cssText = ` + width: 100%; + border-collapse: collapse; + margin-bottom: 20px; + font-family: var(--nl-font-family, 'Courier New', monospace); + font-size: 12px; + `; + + // Table header + const thead = document.createElement('thead'); + thead.innerHTML = ` + + # + Public Key (npub) + Action + + `; + table.appendChild(thead); + + // Table body + const tbody = document.createElement('tbody'); + accounts.forEach(account => { + const row = document.createElement('tr'); + row.style.cssText = 'border: 1px solid #d1d5db;'; + + const indexCell = document.createElement('td'); + indexCell.textContent = account.index; + indexCell.style.cssText = 'padding: 8px; text-align: center; border: 1px solid #d1d5db; font-weight: bold;'; + + const pubkeyCell = document.createElement('td'); + pubkeyCell.style.cssText = 'padding: 8px; border: 1px solid #d1d5db; font-family: monospace; word-break: break-all;'; + + // Show truncated npub for readability + const truncatedNpub = `${account.npub.slice(0, 12)}...${account.npub.slice(-8)}`; + pubkeyCell.innerHTML = ` + ${truncatedNpub}
+ Full: ${account.npub} + `; + + const actionCell = document.createElement('td'); + actionCell.style.cssText = 'padding: 8px; text-align: center; border: 1px solid #d1d5db;'; + + const selectButton = document.createElement('button'); + selectButton.textContent = 'Use'; + selectButton.onclick = () => this._selectAccount(account); + selectButton.style.cssText = ` + padding: 4px 12px; + font-size: 11px; + background: var(--nl-secondary-color); + color: var(--nl-primary-color); + border: 1px solid var(--nl-primary-color); + border-radius: 4px; + cursor: pointer; + font-family: var(--nl-font-family, 'Courier New', monospace); + `; + selectButton.onmouseover = () => { + selectButton.style.borderColor = 'var(--nl-accent-color)'; + }; + selectButton.onmouseout = () => { + selectButton.style.borderColor = 'var(--nl-primary-color)'; + }; + + actionCell.appendChild(selectButton); + + row.appendChild(indexCell); + row.appendChild(pubkeyCell); + row.appendChild(actionCell); + tbody.appendChild(row); + }); + table.appendChild(tbody); + + this.modalBody.appendChild(table); + + // Back button + const backButton = document.createElement('button'); + backButton.textContent = 'Back to Seed Phrase'; + backButton.onclick = () => this._showSeedPhraseScreen(); + backButton.style.cssText = this._getButtonStyle('secondary'); + + this.modalBody.appendChild(backButton); + } + + _selectAccount(account) { + console.log('Selected account:', account.index, account.npub); + + // Use the same auth method as local keys, but with seedphrase identifier + this._setAuthMethod('local', { + secret: account.nsec, + pubkey: account.publicKey, + source: 'seedphrase', + accountIndex: account.index + }); + } + _showOtpScreen() { // Placeholder for OTP functionality this._showError('OTP/DM not yet implemented - coming soon!'); @@ -2503,13 +2317,13 @@ class FloatingTab { // Determine which relays to use const relays = this.options.getUserRelay.length > 0 ? this.options.getUserRelay - : (this.modal?.options?.relays || ['wss://relay.damus.io', 'wss://nos.lol']); + : ['wss://relay.damus.io', 'wss://nos.lol']; console.log('FloatingTab: Fetching profile from relays:', relays); try { // Create a SimplePool instance for querying - const pool = new window.NostrTools.nip46.SimplePool(); + const pool = new window.NostrTools.SimplePool(); // Query for kind 0 (user metadata) events const events = await pool.querySync(relays, { @@ -2532,9 +2346,27 @@ class FloatingTab { const profile = JSON.parse(latestEvent.content); console.log('FloatingTab: Parsed profile:', profile); - // Return relevant profile fields + // Find the best name from any key containing "name" (case-insensitive) + let bestName = null; + const nameKeys = Object.keys(profile).filter(key => + key.toLowerCase().includes('name') && + typeof profile[key] === 'string' && + profile[key].trim().length > 0 + ); + + if (nameKeys.length > 0) { + // Find the shortest name value + bestName = nameKeys + .map(key => profile[key].trim()) + .reduce((shortest, current) => + current.length < shortest.length ? current : shortest + ); + console.log('FloatingTab: Found name keys:', nameKeys, 'selected:', bestName); + } + + // Return relevant profile fields with the best name return { - name: profile.name || null, + name: bestName, display_name: profile.display_name || null, about: profile.about || null, picture: profile.picture || null, @@ -2695,10 +2527,10 @@ class NostrLite { this.options = { theme: 'default', - relays: ['wss://relay.damus.io', 'wss://nos.lol'], methods: { extension: true, local: true, + seedphrase: false, readonly: true, connect: false, otp: false @@ -3127,8 +2959,8 @@ class WindowNostr { } async getRelays() { - // Return configured relays from nostr-lite options - return this.nostrLite.options?.relays || ['wss://relay.damus.io']; + // Return default relays since we removed the relays configuration + return ['wss://relay.damus.io', 'wss://nos.lol']; } get nip04() { diff --git a/api/nostr.bundle.js b/api/nostr.bundle.js index 17634d1..b555c53 100644 --- a/api/nostr.bundle.js +++ b/api/nostr.bundle.js @@ -16,7 +16,7 @@ var NostrTools = (() => { } return to; }; - var __toCommonJS = (mod2) => __copyProps(__defProp({}, "__esModule", { value: true }), mod2); + var __toCommonJS = (mod3) => __copyProps(__defProp({}, "__esModule", { value: true }), mod3); // index.ts var nostr_tools_exports = {}; @@ -35,6 +35,7 @@ var NostrTools = (() => { mergeFilters: () => mergeFilters, nip04: () => nip04_exports, nip05: () => nip05_exports, + nip06: () => nip06_exports, nip10: () => nip10_exports, nip11: () => nip11_exports, nip13: () => nip13_exports, @@ -49,6 +50,7 @@ var NostrTools = (() => { nip39: () => nip39_exports, nip42: () => nip42_exports, nip44: () => nip44_exports, + nip46: () => nip46_exports, nip47: () => nip47_exports, nip54: () => nip54_exports, nip57: () => nip57_exports, @@ -152,9 +154,9 @@ var NostrTools = (() => { function setBigUint64(view, byteOffset, value, isLE4) { if (typeof view.setBigUint64 === "function") return view.setBigUint64(byteOffset, value, isLE4); - const _32n = BigInt(32); + const _32n2 = BigInt(32); const _u32_max = BigInt(4294967295); - const wh = Number(value >> _32n & _u32_max); + const wh = Number(value >> _32n2 & _u32_max); const wl = Number(value & _u32_max); const h = isLE4 ? 4 : 0; const l = isLE4 ? 0 : 4; @@ -678,34 +680,34 @@ var NostrTools = (() => { ; 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)) + return function tonelliFast(Fp3, n) { + const root = Fp3.pow(n, p1div4); + if (!Fp3.eql(Fp3.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)) + return function tonelliSlow(Fp3, n) { + if (Fp3.pow(n, legendreC) === Fp3.neg(Fp3.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 g = Fp3.pow(Fp3.mul(Fp3.ONE, Z), Q); + let x = Fp3.pow(n, Q1div2); + let b = Fp3.pow(n, Q); + while (!Fp3.eql(b, Fp3.ONE)) { + if (Fp3.eql(b, Fp3.ZERO)) + return Fp3.ZERO; let m = 1; - for (let t2 = Fp2.sqr(b); m < r; m++) { - if (Fp2.eql(t2, Fp2.ONE)) + for (let t2 = Fp3.sqr(b); m < r; m++) { + if (Fp3.eql(t2, Fp3.ONE)) break; - t2 = Fp2.sqr(t2); + t2 = Fp3.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); + const ge2 = Fp3.pow(g, _1n2 << BigInt(r - m - 1)); + g = Fp3.sqr(ge2); + x = Fp3.mul(x, ge2); + b = Fp3.mul(b, g); r = m; } return x; @@ -714,22 +716,22 @@ var NostrTools = (() => { 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)) + return function sqrt3mod4(Fp3, n) { + const root = Fp3.pow(n, p1div4); + if (!Fp3.eql(Fp3.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)) + return function sqrt5mod8(Fp3, n) { + const n2 = Fp3.mul(n, _2n2); + const v = Fp3.pow(n2, c1); + const nv = Fp3.mul(n, v); + const i2 = Fp3.mul(Fp3.mul(nv, _2n2), v); + const root = Fp3.mul(nv, Fp3.sub(i2, Fp3.ONE)); + if (!Fp3.eql(Fp3.sqr(root), n)) throw new Error("Cannot find square root"); return root; }; @@ -770,37 +772,37 @@ var NostrTools = (() => { }, initial); return validateObject(field, opts); } - function FpPow(f, num, power) { + function FpPow(f2, num, power) { if (power < _0n2) throw new Error("Expected power > 0"); if (power === _0n2) - return f.ONE; + return f2.ONE; if (power === _1n2) return num; - let p = f.ONE; + let p = f2.ONE; let d = num; while (power > _0n2) { if (power & _1n2) - p = f.mul(p, d); - d = f.sqr(d); + p = f2.mul(p, d); + d = f2.sqr(d); power >>= _1n2; } return p; } - function FpInvertBatch(f, nums) { + function FpInvertBatch(f2, nums) { const tmp = new Array(nums.length); const lastMultiplied = nums.reduce((acc, num, i2) => { - if (f.is0(num)) + if (f2.is0(num)) return acc; tmp[i2] = acc; - return f.mul(acc, num); - }, f.ONE); - const inverted = f.inv(lastMultiplied); + return f2.mul(acc, num); + }, f2.ONE); + const inverted = f2.inv(lastMultiplied); nums.reduceRight((acc, num, i2) => { - if (f.is0(num)) + if (f2.is0(num)) return acc; - tmp[i2] = f.mul(acc, tmp[i2]); - return f.mul(acc, num); + tmp[i2] = f2.mul(acc, tmp[i2]); + return f2.mul(acc, num); }, inverted); return tmp; } @@ -809,14 +811,14 @@ var NostrTools = (() => { const nByteLength = Math.ceil(_nBitLength / 8); return { nBitLength: _nBitLength, nByteLength }; } - function Field(ORDER, bitLen2, isLE4 = false, redef = {}) { + function Field(ORDER, bitLen3, isLE4 = false, redef = {}) { if (ORDER <= _0n2) throw new Error(`Expected Field ORDER > 0, got ${ORDER}`); - const { nBitLength: BITS, nByteLength: BYTES } = nLength(ORDER, bitLen2); + const { nBitLength: BITS, nByteLength: BYTES } = nLength(ORDER, bitLen3); if (BYTES > 2048) throw new Error("Field lengths over 2048 bytes are not supported"); const sqrtP = FpSqrt(ORDER); - const f = Object.freeze({ + const f2 = Object.freeze({ ORDER, BITS, BYTES, @@ -837,15 +839,15 @@ var NostrTools = (() => { 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), + pow: (num, power) => FpPow(f2, 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), + sqrt: redef.sqrt || ((n) => sqrtP(f2, n)), + invertBatch: (lst) => FpInvertBatch(f2, lst), cmov: (a, b, c) => c ? b : a, toBytes: (num) => isLE4 ? numberToBytesLE(num, BYTES) : numberToBytesBE(num, BYTES), fromBytes: (bytes4) => { @@ -854,7 +856,7 @@ var NostrTools = (() => { return isLE4 ? bytesToNumberLE(bytes4) : bytesToNumberBE(bytes4); } }); - return Object.freeze(f); + return Object.freeze(f2); } function getFieldBytesLength(fieldOrder) { if (typeof fieldOrder !== "bigint") @@ -922,7 +924,7 @@ var NostrTools = (() => { wNAF(W, precomputes, n) { const { windows, windowSize } = opts(W); let p = c.ZERO; - let f = c.BASE; + let f2 = c.BASE; const mask = BigInt(2 ** W - 1); const maxNumber = 2 ** W; const shiftBy = BigInt(W); @@ -939,12 +941,12 @@ var NostrTools = (() => { const cond1 = window % 2 !== 0; const cond2 = wbits < 0; if (wbits === 0) { - f = f.add(constTimeNegate(cond1, precomputes[offset1])); + f2 = f2.add(constTimeNegate(cond1, precomputes[offset1])); } else { p = p.add(constTimeNegate(cond2, precomputes[offset2])); } } - return { p, f }; + return { p, f: f2 }; }, wNAFCached(P, precomputesMap, n, transform) { const W = P._WINDOW_SIZE || 1; @@ -992,9 +994,9 @@ var NostrTools = (() => { fromBytes: "function", toBytes: "function" }); - const { endo, Fp: Fp2, a } = opts; + const { endo, Fp: Fp3, a } = opts; if (endo) { - if (!Fp2.eql(a, Fp2.ZERO)) { + if (!Fp3.eql(a, Fp3.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") { @@ -1062,24 +1064,24 @@ var NostrTools = (() => { var _4n2 = BigInt(4); function weierstrassPoints(opts) { const CURVE = validatePointOpts(opts); - const { Fp: Fp2 } = CURVE; + const { Fp: Fp3 } = 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)); + return concatBytes2(Uint8Array.from([4]), Fp3.toBytes(a.x), Fp3.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)); + const x = Fp3.fromBytes(tail.subarray(0, Fp3.BYTES)); + const y = Fp3.fromBytes(tail.subarray(Fp3.BYTES, 2 * Fp3.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); + const x2 = Fp3.sqr(x); + const x3 = Fp3.mul(x2, x); + return Fp3.add(Fp3.add(x3, Fp3.mul(x, a)), b); } - if (!Fp2.eql(Fp2.sqr(CURVE.Gy), weierstrassEquation(CURVE.Gx))) + if (!Fp3.eql(Fp3.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; @@ -1110,31 +1112,31 @@ var NostrTools = (() => { } const pointPrecomputes = /* @__PURE__ */ new Map(); function assertPrjPoint(other) { - if (!(other instanceof Point2)) + if (!(other instanceof Point4)) throw new Error("ProjectivePoint expected"); } - class Point2 { + class Point4 { constructor(px, py, pz) { this.px = px; this.py = py; this.pz = pz; - if (px == null || !Fp2.isValid(px)) + if (px == null || !Fp3.isValid(px)) throw new Error("x required"); - if (py == null || !Fp2.isValid(py)) + if (py == null || !Fp3.isValid(py)) throw new Error("y required"); - if (pz == null || !Fp2.isValid(pz)) + if (pz == null || !Fp3.isValid(pz)) throw new Error("z required"); } static fromAffine(p) { const { x, y } = p || {}; - if (!p || !Fp2.isValid(x) || !Fp2.isValid(y)) + if (!p || !Fp3.isValid(x) || !Fp3.isValid(y)) throw new Error("invalid affine point"); - if (p instanceof Point2) + if (p instanceof Point4) throw new Error("projective point not allowed"); - const is0 = (i2) => Fp2.eql(i2, Fp2.ZERO); + const is0 = (i2) => Fp3.eql(i2, Fp3.ZERO); if (is0(x) && is0(y)) - return Point2.ZERO; - return new Point2(x, y, Fp2.ONE); + return Point4.ZERO; + return new Point4(x, y, Fp3.ONE); } get x() { return this.toAffine().x; @@ -1143,16 +1145,16 @@ var NostrTools = (() => { 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); + const toInv = Fp3.invertBatch(points.map((p) => p.pz)); + return points.map((p, i2) => p.toAffine(toInv[i2])).map(Point4.fromAffine); } static fromHex(hex2) { - const P = Point2.fromAffine(fromBytes(ensureBytes("pointHex", hex2))); + const P = Point4.fromAffine(fromBytes(ensureBytes("pointHex", hex2))); P.assertValidity(); return P; } static fromPrivateKey(privateKey) { - return Point2.BASE.multiply(normPrivateKeyToScalar(privateKey)); + return Point4.BASE.multiply(normPrivateKeyToScalar(privateKey)); } _setWindowSize(windowSize) { this._WINDOW_SIZE = windowSize; @@ -1160,138 +1162,138 @@ var NostrTools = (() => { } assertValidity() { if (this.is0()) { - if (CURVE.allowInfinityPoint && !Fp2.is0(this.py)) + if (CURVE.allowInfinityPoint && !Fp3.is0(this.py)) return; throw new Error("bad point: ZERO"); } const { x, y } = this.toAffine(); - if (!Fp2.isValid(x) || !Fp2.isValid(y)) + if (!Fp3.isValid(x) || !Fp3.isValid(y)) throw new Error("bad point: x or y not FE"); - const left = Fp2.sqr(y); + const left = Fp3.sqr(y); const right = weierstrassEquation(x); - if (!Fp2.eql(left, right)) + if (!Fp3.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); + if (Fp3.isOdd) + return !Fp3.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)); + const U1 = Fp3.eql(Fp3.mul(X1, Z2), Fp3.mul(X2, Z1)); + const U2 = Fp3.eql(Fp3.mul(Y1, Z2), Fp3.mul(Y2, Z1)); return U1 && U2; } negate() { - return new Point2(this.px, Fp2.neg(this.py), this.pz); + return new Point4(this.px, Fp3.neg(this.py), this.pz); } double() { const { a, b } = CURVE; - const b3 = Fp2.mul(b, _3n2); + const b3 = Fp3.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); + let X3 = Fp3.ZERO, Y3 = Fp3.ZERO, Z3 = Fp3.ZERO; + let t0 = Fp3.mul(X1, X1); + let t1 = Fp3.mul(Y1, Y1); + let t2 = Fp3.mul(Z1, Z1); + let t3 = Fp3.mul(X1, Y1); + t3 = Fp3.add(t3, t3); + Z3 = Fp3.mul(X1, Z1); + Z3 = Fp3.add(Z3, Z3); + X3 = Fp3.mul(a, Z3); + Y3 = Fp3.mul(b3, t2); + Y3 = Fp3.add(X3, Y3); + X3 = Fp3.sub(t1, Y3); + Y3 = Fp3.add(t1, Y3); + Y3 = Fp3.mul(X3, Y3); + X3 = Fp3.mul(t3, X3); + Z3 = Fp3.mul(b3, Z3); + t2 = Fp3.mul(a, t2); + t3 = Fp3.sub(t0, t2); + t3 = Fp3.mul(a, t3); + t3 = Fp3.add(t3, Z3); + Z3 = Fp3.add(t0, t0); + t0 = Fp3.add(Z3, t0); + t0 = Fp3.add(t0, t2); + t0 = Fp3.mul(t0, t3); + Y3 = Fp3.add(Y3, t0); + t2 = Fp3.mul(Y1, Z1); + t2 = Fp3.add(t2, t2); + t0 = Fp3.mul(t2, t3); + X3 = Fp3.sub(X3, t0); + Z3 = Fp3.mul(t2, t1); + Z3 = Fp3.add(Z3, Z3); + Z3 = Fp3.add(Z3, Z3); + return new Point4(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; + let X3 = Fp3.ZERO, Y3 = Fp3.ZERO, Z3 = Fp3.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); + const b3 = Fp3.mul(CURVE.b, _3n2); + let t0 = Fp3.mul(X1, X2); + let t1 = Fp3.mul(Y1, Y2); + let t2 = Fp3.mul(Z1, Z2); + let t3 = Fp3.add(X1, Y1); + let t4 = Fp3.add(X2, Y2); + t3 = Fp3.mul(t3, t4); + t4 = Fp3.add(t0, t1); + t3 = Fp3.sub(t3, t4); + t4 = Fp3.add(X1, Z1); + let t5 = Fp3.add(X2, Z2); + t4 = Fp3.mul(t4, t5); + t5 = Fp3.add(t0, t2); + t4 = Fp3.sub(t4, t5); + t5 = Fp3.add(Y1, Z1); + X3 = Fp3.add(Y2, Z2); + t5 = Fp3.mul(t5, X3); + X3 = Fp3.add(t1, t2); + t5 = Fp3.sub(t5, X3); + Z3 = Fp3.mul(a, t4); + X3 = Fp3.mul(b3, t2); + Z3 = Fp3.add(X3, Z3); + X3 = Fp3.sub(t1, Z3); + Z3 = Fp3.add(t1, Z3); + Y3 = Fp3.mul(X3, Z3); + t1 = Fp3.add(t0, t0); + t1 = Fp3.add(t1, t0); + t2 = Fp3.mul(a, t2); + t4 = Fp3.mul(b3, t4); + t1 = Fp3.add(t1, t2); + t2 = Fp3.sub(t0, t2); + t2 = Fp3.mul(a, t2); + t4 = Fp3.add(t4, t2); + t0 = Fp3.mul(t1, t4); + Y3 = Fp3.add(Y3, t0); + t0 = Fp3.mul(t5, t4); + X3 = Fp3.mul(t3, X3); + X3 = Fp3.sub(X3, t0); + t0 = Fp3.mul(t3, t1); + Z3 = Fp3.mul(t5, Z3); + Z3 = Fp3.add(Z3, t0); + return new Point4(X3, Y3, Z3); } subtract(other) { return this.add(other.negate()); } is0() { - return this.equals(Point2.ZERO); + return this.equals(Point4.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); + const toInv = Fp3.invertBatch(comp.map((p) => p.pz)); + return comp.map((p, i2) => p.toAffine(toInv[i2])).map(Point4.fromAffine); }); } multiplyUnsafe(n) { - const I = Point2.ZERO; + const I = Point4.ZERO; if (n === _0n4) return I; assertGE(n); @@ -1317,7 +1319,7 @@ var NostrTools = (() => { k1p = k1p.negate(); if (k2neg) k2p = k2p.negate(); - k2p = new Point2(Fp2.mul(k2p.px, endo.beta), k2p.py, k2p.pz); + k2p = new Point4(Fp3.mul(k2p.px, endo.beta), k2p.py, k2p.pz); return k1p.add(k2p); } multiply(scalar) { @@ -1331,18 +1333,18 @@ var NostrTools = (() => { 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); + k2p = new Point4(Fp3.mul(k2p.px, endo.beta), k2p.py, k2p.pz); point = k1p.add(k2p); fake = f1p.add(f2p); } else { - const { p, f } = this.wNAF(n); + const { p, f: f2 } = this.wNAF(n); point = p; - fake = f; + fake = f2; } - return Point2.normalizeZ([point, fake])[0]; + return Point4.normalizeZ([point, fake])[0]; } multiplyAndAddUnsafe(Q, a, b) { - const G = Point2.BASE; + const G = Point4.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; @@ -1351,13 +1353,13 @@ var NostrTools = (() => { 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); + iz = is0 ? Fp3.ONE : Fp3.inv(z); + const ax = Fp3.mul(x, iz); + const ay = Fp3.mul(y, iz); + const zz = Fp3.mul(z, iz); if (is0) - return { x: Fp2.ZERO, y: Fp2.ZERO }; - if (!Fp2.eql(zz, Fp2.ONE)) + return { x: Fp3.ZERO, y: Fp3.ZERO }; + if (!Fp3.eql(zz, Fp3.ONE)) throw new Error("invZ was invalid"); return { x: ax, y: ay }; } @@ -1366,7 +1368,7 @@ var NostrTools = (() => { if (cofactor === _1n4) return true; if (isTorsionFree) - return isTorsionFree(Point2, this); + return isTorsionFree(Point4, this); throw new Error("isTorsionFree() has not been declared for the elliptic curve"); } clearCofactor() { @@ -1374,24 +1376,24 @@ var NostrTools = (() => { if (cofactor === _1n4) return this; if (clearCofactor) - return clearCofactor(Point2, this); + return clearCofactor(Point4, this); return this.multiplyUnsafe(CURVE.h); } toRawBytes(isCompressed = true) { this.assertValidity(); - return toBytes4(Point2, this, isCompressed); + return toBytes4(Point4, 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); + Point4.BASE = new Point4(CURVE.Gx, CURVE.Gy, Fp3.ONE); + Point4.ZERO = new Point4(Fp3.ZERO, Fp3.ONE, Fp3.ZERO); const _bits = CURVE.nBitLength; - const wnaf = wNAF(Point2, CURVE.endo ? Math.ceil(_bits / 2) : _bits); + const wnaf = wNAF(Point4, CURVE.endo ? Math.ceil(_bits / 2) : _bits); return { CURVE, - ProjectivePoint: Point2, + ProjectivePoint: Point4, normPrivateKeyToScalar, weierstrassEquation, isWithinCurveOrder @@ -1412,11 +1414,11 @@ var NostrTools = (() => { } 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; + const { Fp: Fp3, n: CURVE_ORDER } = CURVE; + const compressedLen = Fp3.BYTES + 1; + const uncompressedLen = 2 * Fp3.BYTES + 1; function isValidFieldElement(num) { - return _0n4 < num && num < Fp2.ORDER; + return _0n4 < num && num < Fp3.ORDER; } function modN2(a) { return mod(a, CURVE_ORDER); @@ -1424,16 +1426,16 @@ var NostrTools = (() => { function invN(a) { return invert(a, CURVE_ORDER); } - const { ProjectivePoint: Point2, normPrivateKeyToScalar, weierstrassEquation, isWithinCurveOrder } = weierstrassPoints({ + const { ProjectivePoint: Point4, normPrivateKeyToScalar, weierstrassEquation, isWithinCurveOrder } = weierstrassPoints({ ...CURVE, toBytes(_c, point, isCompressed) { const a = point.toAffine(); - const x = Fp2.toBytes(a.x); + const x = Fp3.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)); + return cat(Uint8Array.from([4]), x, Fp3.toBytes(a.y)); } }, fromBytes(bytes4) { @@ -1445,15 +1447,15 @@ var NostrTools = (() => { if (!isValidFieldElement(x)) throw new Error("Point is not on curve"); const y2 = weierstrassEquation(x); - let y = Fp2.sqrt(y2); + let y = Fp3.sqrt(y2); const isYOdd = (y & _1n4) === _1n4; const isHeadOdd = (head & 1) === 1; if (isHeadOdd !== isYOdd) - y = Fp2.neg(y); + y = Fp3.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)); + const x = Fp3.fromBytes(tail.subarray(0, Fp3.BYTES)); + const y = Fp3.fromBytes(tail.subarray(Fp3.BYTES, 2 * Fp3.BYTES)); return { x, y }; } else { throw new Error(`Point of length ${len} was invalid. Expected ${compressedLen} compressed bytes or ${uncompressedLen} uncompressed bytes`); @@ -1500,14 +1502,14 @@ var NostrTools = (() => { 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) + if (radj >= Fp3.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 R = Point4.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); + const Q = Point4.BASE.multiplyAndAddUnsafe(R, u1, u2); if (!Q) throw new Error("point at infinify"); Q.assertValidity(); @@ -1532,7 +1534,7 @@ var NostrTools = (() => { return numToNByteStr(this.r) + numToNByteStr(this.s); } } - const utils = { + const utils2 = { isValidPrivateKey(privateKey) { try { normPrivateKeyToScalar(privateKey); @@ -1546,14 +1548,14 @@ var NostrTools = (() => { const length = getMinHashLength(CURVE.n); return mapHashToField(CURVE.randomBytes(length), CURVE.n); }, - precompute(windowSize = 8, point = Point2.BASE) { + precompute(windowSize = 8, point = Point4.BASE) { point._setWindowSize(windowSize); point.multiply(BigInt(3)); return point; } }; function getPublicKey2(privateKey, isCompressed = true) { - return Point2.fromPrivateKey(privateKey).toRawBytes(isCompressed); + return Point4.fromPrivateKey(privateKey).toRawBytes(isCompressed); } function isProbPub(item) { const arr = item instanceof Uint8Array; @@ -1563,7 +1565,7 @@ var NostrTools = (() => { return len === compressedLen || len === uncompressedLen; if (str) return len === 2 * compressedLen || len === 2 * uncompressedLen; - if (item instanceof Point2) + if (item instanceof Point4) return true; return false; } @@ -1572,7 +1574,7 @@ var NostrTools = (() => { 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); + const b = Point4.fromHex(publicB); return b.multiply(normPrivateKeyToScalar(privateA)).toRawBytes(isCompressed); } const bits2int = CURVE.bits2int || function(bytes4) { @@ -1605,7 +1607,7 @@ var NostrTools = (() => { const d = normPrivateKeyToScalar(privateKey); const seedArgs = [int2octets(d), int2octets(h1int)]; if (ent != null) { - const e = ent === true ? randomBytes3(Fp2.BYTES) : ent; + const e = ent === true ? randomBytes3(Fp3.BYTES) : ent; seedArgs.push(ensureBytes("extraEntropy", e)); } const seed = concatBytes2(...seedArgs); @@ -1615,7 +1617,7 @@ var NostrTools = (() => { if (!isWithinCurveOrder(k)) return; const ik = invN(k); - const q = Point2.BASE.multiply(k).toAffine(); + const q = Point4.BASE.multiply(k).toAffine(); const r = modN2(q.x); if (r === _0n4) return; @@ -1640,7 +1642,7 @@ var NostrTools = (() => { const drbg = createHmacDrbg(C.hash.outputLen, C.nByteLength, C.hmac); return drbg(seed, k2sig); } - Point2.BASE._setWindowSize(8); + Point4.BASE._setWindowSize(8); function verify(signature, msgHash, publicKey, opts = defaultVerOpts) { const sg = signature; msgHash = ensureBytes("msgHash", msgHash); @@ -1665,7 +1667,7 @@ var NostrTools = (() => { } else { throw new Error("PARSE"); } - P = Point2.fromHex(publicKey); + P = Point4.fromHex(publicKey); } catch (error) { if (error.message === "PARSE") throw new Error(`signature must be Signature instance, Uint8Array or hex string`); @@ -1680,7 +1682,7 @@ var NostrTools = (() => { const is = invN(s); const u1 = modN2(h * is); const u2 = modN2(r * is); - const R = Point2.BASE.multiplyAndAddUnsafe(P, u1, u2)?.toAffine(); + const R = Point4.BASE.multiplyAndAddUnsafe(P, u1, u2)?.toAffine(); if (!R) return false; const v = modN2(R.x); @@ -1692,9 +1694,9 @@ var NostrTools = (() => { getSharedSecret, sign, verify, - ProjectivePoint: Point2, + ProjectivePoint: Point4, Signature, - utils + utils: utils2 }; } @@ -1784,19 +1786,19 @@ var NostrTools = (() => { 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 _3n5 = 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 b6 = pow2(b3, _3n5, P) * b3 % P; + const b9 = pow2(b6, _3n5, 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 b223 = pow2(b220, _3n5, 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); @@ -2002,6 +2004,13 @@ var NostrTools = (() => { return this._cloneInto(); } }; + var isPlainObject = (obj) => Object.prototype.toString.call(obj) === "[object Object]" && obj.constructor === Object; + function checkOpts(defaults, opts) { + if (opts !== void 0 && (typeof opts !== "object" || !isPlainObject(opts))) + throw new Error("Options should be object or undefined"); + const merged = Object.assign(defaults, opts); + return merged; + } function wrapConstructor2(hashCons) { const hashC = (msg) => hashCons().update(toBytes2(msg)).digest(); const tmp = hashCons(); @@ -2103,9 +2112,9 @@ var NostrTools = (() => { function setBigUint642(view, byteOffset, value, isLE4) { if (typeof view.setBigUint64 === "function") return view.setBigUint64(byteOffset, value, isLE4); - const _32n = BigInt(32); + const _32n2 = BigInt(32); const _u32_max = BigInt(4294967295); - const wh = Number(value >> _32n & _u32_max); + const wh = Number(value >> _32n2 & _u32_max); const wl = Number(value & _u32_max); const h = isLE4 ? 4 : 0; const l = isLE4 ? 0 : 4; @@ -2742,11 +2751,11 @@ var NostrTools = (() => { if (filter.authors && filter.authors.indexOf(event.pubkey) === -1) { return false; } - for (let f in filter) { - if (f[0] === "#") { - let tagName = f.slice(1); + for (let f2 in filter) { + if (f2[0] === "#") { + let tagName = f2.slice(1); let values = filter[`#${tagName}`]; - if (values && !event.tags.find(([t, v]) => t === f.slice(1) && values.indexOf(v) !== -1)) + if (values && !event.tags.find(([t, v]) => t === f2.slice(1) && values.indexOf(v) !== -1)) return false; } } @@ -2977,22 +2986,18 @@ var NostrTools = (() => { 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._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._connected = false; + this.connectionPromise = void 0; + this.onclose?.(); + this.closeAllSubscriptions("relay connection closed"); }; this.ws.onmessage = this._onmessage.bind(this); }); @@ -3027,8 +3032,8 @@ var NostrTools = (() => { } else { this.closeAllSubscriptions("pingpong timed out"); this._connected = false; - this.ws?.close(); this.onclose?.(); + this.ws?.close(); } } } @@ -3193,8 +3198,8 @@ var NostrTools = (() => { close() { this.closeAllSubscriptions("relay connection closed by us"); this._connected = false; - this.ws?.close(); this.onclose?.(); + this.ws?.close(); } _onmessage(ev) { this.incomingMessageQueue.enqueue(ev.data); @@ -3334,8 +3339,8 @@ var NostrTools = (() => { 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] }); + for (let f2 = 0; f2 < filters.length; f2++) { + request.push({ url, filter: filters[f2] }); } } } @@ -3758,6 +3763,34 @@ var NostrTools = (() => { } }; } + function checksum(len, fn) { + assertNumber(len); + if (typeof fn !== "function") + throw new Error("checksum fn should be function"); + return { + encode(data) { + if (!(data instanceof Uint8Array)) + throw new Error("checksum.encode: input should be Uint8Array"); + const checksum2 = fn(data).slice(0, len); + const res = new Uint8Array(data.length + len); + res.set(data); + res.set(checksum2, data.length); + return res; + }, + decode(data) { + if (!(data instanceof Uint8Array)) + throw new Error("checksum.decode: input should be Uint8Array"); + const payload = data.slice(0, -len); + const newChecksum = fn(payload).slice(0, len); + const oldChecksum = data.slice(-len); + for (let i2 = 0; i2 < len; i2++) + if (newChecksum[i2] !== oldChecksum[i2]) + throw new Error("Invalid checksum"); + return payload; + } + }; + } + var utils = { alphabet, chain, checksum, radix, radix2, join, padding }; 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("")); @@ -3793,6 +3826,7 @@ var NostrTools = (() => { return Uint8Array.from(res); } }; + var base58check = (sha2563) => chain(checksum(4, (data) => sha2563(sha2563(data))), base58); var BECH_ALPHABET = chain(alphabet("qpzry9x8gf2tvdw0s3jn54khce6mua7l"), join("")); var POLYMOD_GENERATORS = [996825010, 642813549, 513874426, 1027748829, 705979059]; function bech32Polymod(pre) { @@ -4215,7 +4249,7 @@ var NostrTools = (() => { throw new Error(`Uint8Array expected, got ${typeof data}`); return data; } - function checkOpts(defaults, opts) { + function checkOpts2(defaults, opts) { if (opts == null || typeof opts !== "object") throw new Error("options must be defined"); const merged = Object.assign(defaults, opts); @@ -4236,9 +4270,9 @@ var NostrTools = (() => { function setBigUint643(view, byteOffset, value, isLE4) { if (typeof view.setBigUint64 === "function") return view.setBigUint64(byteOffset, value, isLE4); - const _32n = BigInt(32); + const _32n2 = BigInt(32); const _u32_max = BigInt(4294967295); - const wh = Number(value >> _32n & _u32_max); + const wh = Number(value >> _32n2 & _u32_max); const wl = Number(value & _u32_max); const h = isLE4 ? 4 : 0; const l = isLE4 ? 0 : 4; @@ -4788,6 +4822,42 @@ var NostrTools = (() => { } }; }); + var cfb = wrapCipher({ blockSize: 16, nonceLength: 16 }, function cfb2(key, iv) { + bytes3(key); + bytes3(iv, 16); + function processCfb(src, isEncrypt, dst) { + const xk = expandKeyLE(key); + const srcLen = src.length; + dst = getDst(srcLen, dst); + const src32 = u32(src); + const dst32 = u32(dst); + const next32 = isEncrypt ? dst32 : src32; + const n32 = u32(iv); + let s0 = n32[0], s1 = n32[1], s2 = n32[2], s3 = n32[3]; + for (let i2 = 0; i2 + 4 <= src32.length; ) { + const { s0: e0, s1: e1, s2: e2, s3: e3 } = encrypt(xk, s0, s1, s2, s3); + dst32[i2 + 0] = src32[i2 + 0] ^ e0; + dst32[i2 + 1] = src32[i2 + 1] ^ e1; + dst32[i2 + 2] = src32[i2 + 2] ^ e2; + dst32[i2 + 3] = src32[i2 + 3] ^ e3; + s0 = next32[i2++], s1 = next32[i2++], s2 = next32[i2++], s3 = next32[i2++]; + } + const start = BLOCK_SIZE2 * Math.floor(src32.length / BLOCK_SIZE32); + if (start < srcLen) { + ({ s0, s1, s2, s3 } = encrypt(xk, s0, s1, s2, s3)); + const buf = u8(new Uint32Array([s0, s1, s2, s3])); + for (let i2 = start, pos = 0; i2 < srcLen; i2++, pos++) + dst[i2] = src[i2] ^ buf[pos]; + buf.fill(0); + } + xk.fill(0); + return dst; + } + return { + encrypt: (plaintext, dst) => processCfb(plaintext, true, dst), + decrypt: (ciphertext, dst) => processCfb(ciphertext, false, dst) + }; + }); function computeTag(fn, isLE4, key, data, AAD) { const h = fn.create(key, data.length + (AAD?.length || 0)); if (AAD) @@ -5023,6 +5093,4313 @@ var NostrTools = (() => { return res ? res.pubkey === pubkey : false; } + // nip06.ts + var nip06_exports = {}; + __export(nip06_exports, { + accountFromExtendedKey: () => accountFromExtendedKey, + accountFromSeedWords: () => accountFromSeedWords, + extendedKeysFromSeedWords: () => extendedKeysFromSeedWords, + generateSeedWords: () => generateSeedWords, + privateKeyFromSeedWords: () => privateKeyFromSeedWords, + validateWords: () => validateWords + }); + + // node_modules/@scure/bip39/esm/wordlists/english.js + var wordlist = `abandon +ability +able +about +above +absent +absorb +abstract +absurd +abuse +access +accident +account +accuse +achieve +acid +acoustic +acquire +across +act +action +actor +actress +actual +adapt +add +addict +address +adjust +admit +adult +advance +advice +aerobic +affair +afford +afraid +again +age +agent +agree +ahead +aim +air +airport +aisle +alarm +album +alcohol +alert +alien +all +alley +allow +almost +alone +alpha +already +also +alter +always +amateur +amazing +among +amount +amused +analyst +anchor +ancient +anger +angle +angry +animal +ankle +announce +annual +another +answer +antenna +antique +anxiety +any +apart +apology +appear +apple +approve +april +arch +arctic +area +arena +argue +arm +armed +armor +army +around +arrange +arrest +arrive +arrow +art +artefact +artist +artwork +ask +aspect +assault +asset +assist +assume +asthma +athlete +atom +attack +attend +attitude +attract +auction +audit +august +aunt +author +auto +autumn +average +avocado +avoid +awake +aware +away +awesome +awful +awkward +axis +baby +bachelor +bacon +badge +bag +balance +balcony +ball +bamboo +banana +banner +bar +barely +bargain +barrel +base +basic +basket +battle +beach +bean +beauty +because +become +beef +before +begin +behave +behind +believe +below +belt +bench +benefit +best +betray +better +between +beyond +bicycle +bid +bike +bind +biology +bird +birth +bitter +black +blade +blame +blanket +blast +bleak +bless +blind +blood +blossom +blouse +blue +blur +blush +board +boat +body +boil +bomb +bone +bonus +book +boost +border +boring +borrow +boss +bottom +bounce +box +boy +bracket +brain +brand +brass +brave +bread +breeze +brick +bridge +brief +bright +bring +brisk +broccoli +broken +bronze +broom +brother +brown +brush +bubble +buddy +budget +buffalo +build +bulb +bulk +bullet +bundle +bunker +burden +burger +burst +bus +business +busy +butter +buyer +buzz +cabbage +cabin +cable +cactus +cage +cake +call +calm +camera +camp +can +canal +cancel +candy +cannon +canoe +canvas +canyon +capable +capital +captain +car +carbon +card +cargo +carpet +carry +cart +case +cash +casino +castle +casual +cat +catalog +catch +category +cattle +caught +cause +caution +cave +ceiling +celery +cement +census +century +cereal +certain +chair +chalk +champion +change +chaos +chapter +charge +chase +chat +cheap +check +cheese +chef +cherry +chest +chicken +chief +child +chimney +choice +choose +chronic +chuckle +chunk +churn +cigar +cinnamon +circle +citizen +city +civil +claim +clap +clarify +claw +clay +clean +clerk +clever +click +client +cliff +climb +clinic +clip +clock +clog +close +cloth +cloud +clown +club +clump +cluster +clutch +coach +coast +coconut +code +coffee +coil +coin +collect +color +column +combine +come +comfort +comic +common +company +concert +conduct +confirm +congress +connect +consider +control +convince +cook +cool +copper +copy +coral +core +corn +correct +cost +cotton +couch +country +couple +course +cousin +cover +coyote +crack +cradle +craft +cram +crane +crash +crater +crawl +crazy +cream +credit +creek +crew +cricket +crime +crisp +critic +crop +cross +crouch +crowd +crucial +cruel +cruise +crumble +crunch +crush +cry +crystal +cube +culture +cup +cupboard +curious +current +curtain +curve +cushion +custom +cute +cycle +dad +damage +damp +dance +danger +daring +dash +daughter +dawn +day +deal +debate +debris +decade +december +decide +decline +decorate +decrease +deer +defense +define +defy +degree +delay +deliver +demand +demise +denial +dentist +deny +depart +depend +deposit +depth +deputy +derive +describe +desert +design +desk +despair +destroy +detail +detect +develop +device +devote +diagram +dial +diamond +diary +dice +diesel +diet +differ +digital +dignity +dilemma +dinner +dinosaur +direct +dirt +disagree +discover +disease +dish +dismiss +disorder +display +distance +divert +divide +divorce +dizzy +doctor +document +dog +doll +dolphin +domain +donate +donkey +donor +door +dose +double +dove +draft +dragon +drama +drastic +draw +dream +dress +drift +drill +drink +drip +drive +drop +drum +dry +duck +dumb +dune +during +dust +dutch +duty +dwarf +dynamic +eager +eagle +early +earn +earth +easily +east +easy +echo +ecology +economy +edge +edit +educate +effort +egg +eight +either +elbow +elder +electric +elegant +element +elephant +elevator +elite +else +embark +embody +embrace +emerge +emotion +employ +empower +empty +enable +enact +end +endless +endorse +enemy +energy +enforce +engage +engine +enhance +enjoy +enlist +enough +enrich +enroll +ensure +enter +entire +entry +envelope +episode +equal +equip +era +erase +erode +erosion +error +erupt +escape +essay +essence +estate +eternal +ethics +evidence +evil +evoke +evolve +exact +example +excess +exchange +excite +exclude +excuse +execute +exercise +exhaust +exhibit +exile +exist +exit +exotic +expand +expect +expire +explain +expose +express +extend +extra +eye +eyebrow +fabric +face +faculty +fade +faint +faith +fall +false +fame +family +famous +fan +fancy +fantasy +farm +fashion +fat +fatal +father +fatigue +fault +favorite +feature +february +federal +fee +feed +feel +female +fence +festival +fetch +fever +few +fiber +fiction +field +figure +file +film +filter +final +find +fine +finger +finish +fire +firm +first +fiscal +fish +fit +fitness +fix +flag +flame +flash +flat +flavor +flee +flight +flip +float +flock +floor +flower +fluid +flush +fly +foam +focus +fog +foil +fold +follow +food +foot +force +forest +forget +fork +fortune +forum +forward +fossil +foster +found +fox +fragile +frame +frequent +fresh +friend +fringe +frog +front +frost +frown +frozen +fruit +fuel +fun +funny +furnace +fury +future +gadget +gain +galaxy +gallery +game +gap +garage +garbage +garden +garlic +garment +gas +gasp +gate +gather +gauge +gaze +general +genius +genre +gentle +genuine +gesture +ghost +giant +gift +giggle +ginger +giraffe +girl +give +glad +glance +glare +glass +glide +glimpse +globe +gloom +glory +glove +glow +glue +goat +goddess +gold +good +goose +gorilla +gospel +gossip +govern +gown +grab +grace +grain +grant +grape +grass +gravity +great +green +grid +grief +grit +grocery +group +grow +grunt +guard +guess +guide +guilt +guitar +gun +gym +habit +hair +half +hammer +hamster +hand +happy +harbor +hard +harsh +harvest +hat +have +hawk +hazard +head +health +heart +heavy +hedgehog +height +hello +helmet +help +hen +hero +hidden +high +hill +hint +hip +hire +history +hobby +hockey +hold +hole +holiday +hollow +home +honey +hood +hope +horn +horror +horse +hospital +host +hotel +hour +hover +hub +huge +human +humble +humor +hundred +hungry +hunt +hurdle +hurry +hurt +husband +hybrid +ice +icon +idea +identify +idle +ignore +ill +illegal +illness +image +imitate +immense +immune +impact +impose +improve +impulse +inch +include +income +increase +index +indicate +indoor +industry +infant +inflict +inform +inhale +inherit +initial +inject +injury +inmate +inner +innocent +input +inquiry +insane +insect +inside +inspire +install +intact +interest +into +invest +invite +involve +iron +island +isolate +issue +item +ivory +jacket +jaguar +jar +jazz +jealous +jeans +jelly +jewel +job +join +joke +journey +joy +judge +juice +jump +jungle +junior +junk +just +kangaroo +keen +keep +ketchup +key +kick +kid +kidney +kind +kingdom +kiss +kit +kitchen +kite +kitten +kiwi +knee +knife +knock +know +lab +label +labor +ladder +lady +lake +lamp +language +laptop +large +later +latin +laugh +laundry +lava +law +lawn +lawsuit +layer +lazy +leader +leaf +learn +leave +lecture +left +leg +legal +legend +leisure +lemon +lend +length +lens +leopard +lesson +letter +level +liar +liberty +library +license +life +lift +light +like +limb +limit +link +lion +liquid +list +little +live +lizard +load +loan +lobster +local +lock +logic +lonely +long +loop +lottery +loud +lounge +love +loyal +lucky +luggage +lumber +lunar +lunch +luxury +lyrics +machine +mad +magic +magnet +maid +mail +main +major +make +mammal +man +manage +mandate +mango +mansion +manual +maple +marble +march +margin +marine +market +marriage +mask +mass +master +match +material +math +matrix +matter +maximum +maze +meadow +mean +measure +meat +mechanic +medal +media +melody +melt +member +memory +mention +menu +mercy +merge +merit +merry +mesh +message +metal +method +middle +midnight +milk +million +mimic +mind +minimum +minor +minute +miracle +mirror +misery +miss +mistake +mix +mixed +mixture +mobile +model +modify +mom +moment +monitor +monkey +monster +month +moon +moral +more +morning +mosquito +mother +motion +motor +mountain +mouse +move +movie +much +muffin +mule +multiply +muscle +museum +mushroom +music +must +mutual +myself +mystery +myth +naive +name +napkin +narrow +nasty +nation +nature +near +neck +need +negative +neglect +neither +nephew +nerve +nest +net +network +neutral +never +news +next +nice +night +noble +noise +nominee +noodle +normal +north +nose +notable +note +nothing +notice +novel +now +nuclear +number +nurse +nut +oak +obey +object +oblige +obscure +observe +obtain +obvious +occur +ocean +october +odor +off +offer +office +often +oil +okay +old +olive +olympic +omit +once +one +onion +online +only +open +opera +opinion +oppose +option +orange +orbit +orchard +order +ordinary +organ +orient +original +orphan +ostrich +other +outdoor +outer +output +outside +oval +oven +over +own +owner +oxygen +oyster +ozone +pact +paddle +page +pair +palace +palm +panda +panel +panic +panther +paper +parade +parent +park +parrot +party +pass +patch +path +patient +patrol +pattern +pause +pave +payment +peace +peanut +pear +peasant +pelican +pen +penalty +pencil +people +pepper +perfect +permit +person +pet +phone +photo +phrase +physical +piano +picnic +picture +piece +pig +pigeon +pill +pilot +pink +pioneer +pipe +pistol +pitch +pizza +place +planet +plastic +plate +play +please +pledge +pluck +plug +plunge +poem +poet +point +polar +pole +police +pond +pony +pool +popular +portion +position +possible +post +potato +pottery +poverty +powder +power +practice +praise +predict +prefer +prepare +present +pretty +prevent +price +pride +primary +print +priority +prison +private +prize +problem +process +produce +profit +program +project +promote +proof +property +prosper +protect +proud +provide +public +pudding +pull +pulp +pulse +pumpkin +punch +pupil +puppy +purchase +purity +purpose +purse +push +put +puzzle +pyramid +quality +quantum +quarter +question +quick +quit +quiz +quote +rabbit +raccoon +race +rack +radar +radio +rail +rain +raise +rally +ramp +ranch +random +range +rapid +rare +rate +rather +raven +raw +razor +ready +real +reason +rebel +rebuild +recall +receive +recipe +record +recycle +reduce +reflect +reform +refuse +region +regret +regular +reject +relax +release +relief +rely +remain +remember +remind +remove +render +renew +rent +reopen +repair +repeat +replace +report +require +rescue +resemble +resist +resource +response +result +retire +retreat +return +reunion +reveal +review +reward +rhythm +rib +ribbon +rice +rich +ride +ridge +rifle +right +rigid +ring +riot +ripple +risk +ritual +rival +river +road +roast +robot +robust +rocket +romance +roof +rookie +room +rose +rotate +rough +round +route +royal +rubber +rude +rug +rule +run +runway +rural +sad +saddle +sadness +safe +sail +salad +salmon +salon +salt +salute +same +sample +sand +satisfy +satoshi +sauce +sausage +save +say +scale +scan +scare +scatter +scene +scheme +school +science +scissors +scorpion +scout +scrap +screen +script +scrub +sea +search +season +seat +second +secret +section +security +seed +seek +segment +select +sell +seminar +senior +sense +sentence +series +service +session +settle +setup +seven +shadow +shaft +shallow +share +shed +shell +sheriff +shield +shift +shine +ship +shiver +shock +shoe +shoot +shop +short +shoulder +shove +shrimp +shrug +shuffle +shy +sibling +sick +side +siege +sight +sign +silent +silk +silly +silver +similar +simple +since +sing +siren +sister +situate +six +size +skate +sketch +ski +skill +skin +skirt +skull +slab +slam +sleep +slender +slice +slide +slight +slim +slogan +slot +slow +slush +small +smart +smile +smoke +smooth +snack +snake +snap +sniff +snow +soap +soccer +social +sock +soda +soft +solar +soldier +solid +solution +solve +someone +song +soon +sorry +sort +soul +sound +soup +source +south +space +spare +spatial +spawn +speak +special +speed +spell +spend +sphere +spice +spider +spike +spin +spirit +split +spoil +sponsor +spoon +sport +spot +spray +spread +spring +spy +square +squeeze +squirrel +stable +stadium +staff +stage +stairs +stamp +stand +start +state +stay +steak +steel +stem +step +stereo +stick +still +sting +stock +stomach +stone +stool +story +stove +strategy +street +strike +strong +struggle +student +stuff +stumble +style +subject +submit +subway +success +such +sudden +suffer +sugar +suggest +suit +summer +sun +sunny +sunset +super +supply +supreme +sure +surface +surge +surprise +surround +survey +suspect +sustain +swallow +swamp +swap +swarm +swear +sweet +swift +swim +swing +switch +sword +symbol +symptom +syrup +system +table +tackle +tag +tail +talent +talk +tank +tape +target +task +taste +tattoo +taxi +teach +team +tell +ten +tenant +tennis +tent +term +test +text +thank +that +theme +then +theory +there +they +thing +this +thought +three +thrive +throw +thumb +thunder +ticket +tide +tiger +tilt +timber +time +tiny +tip +tired +tissue +title +toast +tobacco +today +toddler +toe +together +toilet +token +tomato +tomorrow +tone +tongue +tonight +tool +tooth +top +topic +topple +torch +tornado +tortoise +toss +total +tourist +toward +tower +town +toy +track +trade +traffic +tragic +train +transfer +trap +trash +travel +tray +treat +tree +trend +trial +tribe +trick +trigger +trim +trip +trophy +trouble +truck +true +truly +trumpet +trust +truth +try +tube +tuition +tumble +tuna +tunnel +turkey +turn +turtle +twelve +twenty +twice +twin +twist +two +type +typical +ugly +umbrella +unable +unaware +uncle +uncover +under +undo +unfair +unfold +unhappy +uniform +unique +unit +universe +unknown +unlock +until +unusual +unveil +update +upgrade +uphold +upon +upper +upset +urban +urge +usage +use +used +useful +useless +usual +utility +vacant +vacuum +vague +valid +valley +valve +van +vanish +vapor +various +vast +vault +vehicle +velvet +vendor +venture +venue +verb +verify +version +very +vessel +veteran +viable +vibrant +vicious +victory +video +view +village +vintage +violin +virtual +virus +visa +visit +visual +vital +vivid +vocal +voice +void +volcano +volume +vote +voyage +wage +wagon +wait +walk +wall +walnut +want +warfare +warm +warrior +wash +wasp +waste +water +wave +way +wealth +weapon +wear +weasel +weather +web +wedding +weekend +weird +welcome +west +wet +whale +what +wheat +wheel +when +where +whip +whisper +wide +width +wife +wild +will +win +window +wine +wing +wink +winner +winter +wire +wisdom +wise +wish +witness +wolf +woman +wonder +wood +wool +word +work +world +worry +worth +wrap +wreck +wrestle +wrist +write +wrong +yard +year +yellow +you +young +youth +zebra +zero +zone +zoo`.split("\n"); + + // 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/pbkdf2.js + function pbkdf2Init(hash3, _password, _salt, _opts) { + assert_default.hash(hash3); + const opts = checkOpts({ dkLen: 32, asyncTick: 10 }, _opts); + const { c, dkLen, asyncTick } = opts; + assert_default.number(c); + assert_default.number(dkLen); + assert_default.number(asyncTick); + if (c < 1) + throw new Error("PBKDF2: iterations (c) should be >= 1"); + const password = toBytes2(_password); + const salt2 = toBytes2(_salt); + const DK = new Uint8Array(dkLen); + const PRF = hmac2.create(hash3, password); + const PRFSalt = PRF._cloneInto().update(salt2); + return { c, dkLen, asyncTick, DK, PRF, PRFSalt }; + } + function pbkdf2Output(PRF, PRFSalt, DK, prfW, u) { + PRF.destroy(); + PRFSalt.destroy(); + if (prfW) + prfW.destroy(); + u.fill(0); + return DK; + } + function pbkdf2(hash3, password, salt2, opts) { + const { c, dkLen, DK, PRF, PRFSalt } = pbkdf2Init(hash3, password, salt2, opts); + let prfW; + const arr = new Uint8Array(4); + const view = createView2(arr); + const u = new Uint8Array(PRF.outputLen); + for (let ti = 1, pos = 0; pos < dkLen; ti++, pos += PRF.outputLen) { + const Ti = DK.subarray(pos, pos + PRF.outputLen); + view.setInt32(0, ti, false); + (prfW = PRFSalt._cloneInto(prfW)).update(arr).digestInto(u); + Ti.set(u.subarray(0, Ti.length)); + for (let ui = 1; ui < c; ui++) { + PRF._cloneInto(prfW).update(u).digestInto(u); + for (let i2 = 0; i2 < Ti.length; i2++) + Ti[i2] ^= u[i2]; + } + } + return pbkdf2Output(PRF, PRFSalt, DK, prfW, u); + } + + // node_modules/@noble/hashes/esm/_u64.js + var U32_MASK64 = BigInt(2 ** 32 - 1); + var _32n = BigInt(32); + function fromBig(n, le = false) { + if (le) + return { h: Number(n & U32_MASK64), l: Number(n >> _32n & U32_MASK64) }; + return { h: Number(n >> _32n & U32_MASK64) | 0, l: Number(n & U32_MASK64) | 0 }; + } + function split(lst, le = false) { + let Ah = new Uint32Array(lst.length); + let Al = new Uint32Array(lst.length); + for (let i2 = 0; i2 < lst.length; i2++) { + const { h, l } = fromBig(lst[i2], le); + [Ah[i2], Al[i2]] = [h, l]; + } + return [Ah, Al]; + } + var toBig = (h, l) => BigInt(h >>> 0) << _32n | BigInt(l >>> 0); + var shrSH = (h, l, s) => h >>> s; + var shrSL = (h, l, s) => h << 32 - s | l >>> s; + var rotrSH = (h, l, s) => h >>> s | l << 32 - s; + var rotrSL = (h, l, s) => h << 32 - s | l >>> s; + var rotrBH = (h, l, s) => h << 64 - s | l >>> s - 32; + var rotrBL = (h, l, s) => h >>> s - 32 | l << 64 - s; + var rotr32H = (h, l) => l; + var rotr32L = (h, l) => h; + var rotlSH = (h, l, s) => h << s | l >>> 32 - s; + var rotlSL = (h, l, s) => l << s | h >>> 32 - s; + var rotlBH = (h, l, s) => l << s - 32 | h >>> 64 - s; + var rotlBL = (h, l, s) => h << s - 32 | l >>> 64 - s; + function add(Ah, Al, Bh, Bl) { + const l = (Al >>> 0) + (Bl >>> 0); + return { h: Ah + Bh + (l / 2 ** 32 | 0) | 0, l: l | 0 }; + } + var add3L = (Al, Bl, Cl) => (Al >>> 0) + (Bl >>> 0) + (Cl >>> 0); + var add3H = (low, Ah, Bh, Ch) => Ah + Bh + Ch + (low / 2 ** 32 | 0) | 0; + var add4L = (Al, Bl, Cl, Dl) => (Al >>> 0) + (Bl >>> 0) + (Cl >>> 0) + (Dl >>> 0); + var add4H = (low, Ah, Bh, Ch, Dh) => Ah + Bh + Ch + Dh + (low / 2 ** 32 | 0) | 0; + var add5L = (Al, Bl, Cl, Dl, El) => (Al >>> 0) + (Bl >>> 0) + (Cl >>> 0) + (Dl >>> 0) + (El >>> 0); + var add5H = (low, Ah, Bh, Ch, Dh, Eh) => Ah + Bh + Ch + Dh + Eh + (low / 2 ** 32 | 0) | 0; + var u64 = { + fromBig, + split, + toBig, + shrSH, + shrSL, + rotrSH, + rotrSL, + rotrBH, + rotrBL, + rotr32H, + rotr32L, + rotlSH, + rotlSL, + rotlBH, + rotlBL, + add, + add3L, + add3H, + add4L, + add4H, + add5H, + add5L + }; + var u64_default = u64; + + // node_modules/@noble/hashes/esm/sha512.js + var [SHA512_Kh, SHA512_Kl] = u64_default.split([ + "0x428a2f98d728ae22", + "0x7137449123ef65cd", + "0xb5c0fbcfec4d3b2f", + "0xe9b5dba58189dbbc", + "0x3956c25bf348b538", + "0x59f111f1b605d019", + "0x923f82a4af194f9b", + "0xab1c5ed5da6d8118", + "0xd807aa98a3030242", + "0x12835b0145706fbe", + "0x243185be4ee4b28c", + "0x550c7dc3d5ffb4e2", + "0x72be5d74f27b896f", + "0x80deb1fe3b1696b1", + "0x9bdc06a725c71235", + "0xc19bf174cf692694", + "0xe49b69c19ef14ad2", + "0xefbe4786384f25e3", + "0x0fc19dc68b8cd5b5", + "0x240ca1cc77ac9c65", + "0x2de92c6f592b0275", + "0x4a7484aa6ea6e483", + "0x5cb0a9dcbd41fbd4", + "0x76f988da831153b5", + "0x983e5152ee66dfab", + "0xa831c66d2db43210", + "0xb00327c898fb213f", + "0xbf597fc7beef0ee4", + "0xc6e00bf33da88fc2", + "0xd5a79147930aa725", + "0x06ca6351e003826f", + "0x142929670a0e6e70", + "0x27b70a8546d22ffc", + "0x2e1b21385c26c926", + "0x4d2c6dfc5ac42aed", + "0x53380d139d95b3df", + "0x650a73548baf63de", + "0x766a0abb3c77b2a8", + "0x81c2c92e47edaee6", + "0x92722c851482353b", + "0xa2bfe8a14cf10364", + "0xa81a664bbc423001", + "0xc24b8b70d0f89791", + "0xc76c51a30654be30", + "0xd192e819d6ef5218", + "0xd69906245565a910", + "0xf40e35855771202a", + "0x106aa07032bbd1b8", + "0x19a4c116b8d2d0c8", + "0x1e376c085141ab53", + "0x2748774cdf8eeb99", + "0x34b0bcb5e19b48a8", + "0x391c0cb3c5c95a63", + "0x4ed8aa4ae3418acb", + "0x5b9cca4f7763e373", + "0x682e6ff3d6b2b8a3", + "0x748f82ee5defb2fc", + "0x78a5636f43172f60", + "0x84c87814a1f0ab72", + "0x8cc702081a6439ec", + "0x90befffa23631e28", + "0xa4506cebde82bde9", + "0xbef9a3f7b2c67915", + "0xc67178f2e372532b", + "0xca273eceea26619c", + "0xd186b8c721c0c207", + "0xeada7dd6cde0eb1e", + "0xf57d4f7fee6ed178", + "0x06f067aa72176fba", + "0x0a637dc5a2c898a6", + "0x113f9804bef90dae", + "0x1b710b35131c471b", + "0x28db77f523047d84", + "0x32caab7b40c72493", + "0x3c9ebe0a15c9bebc", + "0x431d67c49c100d4c", + "0x4cc5d4becb3e42b6", + "0x597f299cfc657e2a", + "0x5fcb6fab3ad6faec", + "0x6c44198c4a475817" + ].map((n) => BigInt(n))); + var SHA512_W_H = new Uint32Array(80); + var SHA512_W_L = new Uint32Array(80); + var SHA512 = class extends SHA22 { + constructor() { + super(128, 64, 16, false); + this.Ah = 1779033703 | 0; + this.Al = 4089235720 | 0; + this.Bh = 3144134277 | 0; + this.Bl = 2227873595 | 0; + this.Ch = 1013904242 | 0; + this.Cl = 4271175723 | 0; + this.Dh = 2773480762 | 0; + this.Dl = 1595750129 | 0; + this.Eh = 1359893119 | 0; + this.El = 2917565137 | 0; + this.Fh = 2600822924 | 0; + this.Fl = 725511199 | 0; + this.Gh = 528734635 | 0; + this.Gl = 4215389547 | 0; + this.Hh = 1541459225 | 0; + this.Hl = 327033209 | 0; + } + get() { + const { Ah, Al, Bh, Bl, Ch, Cl, Dh, Dl, Eh, El, Fh, Fl, Gh, Gl, Hh, Hl } = this; + return [Ah, Al, Bh, Bl, Ch, Cl, Dh, Dl, Eh, El, Fh, Fl, Gh, Gl, Hh, Hl]; + } + set(Ah, Al, Bh, Bl, Ch, Cl, Dh, Dl, Eh, El, Fh, Fl, Gh, Gl, Hh, Hl) { + this.Ah = Ah | 0; + this.Al = Al | 0; + this.Bh = Bh | 0; + this.Bl = Bl | 0; + this.Ch = Ch | 0; + this.Cl = Cl | 0; + this.Dh = Dh | 0; + this.Dl = Dl | 0; + this.Eh = Eh | 0; + this.El = El | 0; + this.Fh = Fh | 0; + this.Fl = Fl | 0; + this.Gh = Gh | 0; + this.Gl = Gl | 0; + this.Hh = Hh | 0; + this.Hl = Hl | 0; + } + process(view, offset) { + for (let i2 = 0; i2 < 16; i2++, offset += 4) { + SHA512_W_H[i2] = view.getUint32(offset); + SHA512_W_L[i2] = view.getUint32(offset += 4); + } + for (let i2 = 16; i2 < 80; i2++) { + const W15h = SHA512_W_H[i2 - 15] | 0; + const W15l = SHA512_W_L[i2 - 15] | 0; + const s0h = u64_default.rotrSH(W15h, W15l, 1) ^ u64_default.rotrSH(W15h, W15l, 8) ^ u64_default.shrSH(W15h, W15l, 7); + const s0l = u64_default.rotrSL(W15h, W15l, 1) ^ u64_default.rotrSL(W15h, W15l, 8) ^ u64_default.shrSL(W15h, W15l, 7); + const W2h = SHA512_W_H[i2 - 2] | 0; + const W2l = SHA512_W_L[i2 - 2] | 0; + const s1h = u64_default.rotrSH(W2h, W2l, 19) ^ u64_default.rotrBH(W2h, W2l, 61) ^ u64_default.shrSH(W2h, W2l, 6); + const s1l = u64_default.rotrSL(W2h, W2l, 19) ^ u64_default.rotrBL(W2h, W2l, 61) ^ u64_default.shrSL(W2h, W2l, 6); + const SUMl = u64_default.add4L(s0l, s1l, SHA512_W_L[i2 - 7], SHA512_W_L[i2 - 16]); + const SUMh = u64_default.add4H(SUMl, s0h, s1h, SHA512_W_H[i2 - 7], SHA512_W_H[i2 - 16]); + SHA512_W_H[i2] = SUMh | 0; + SHA512_W_L[i2] = SUMl | 0; + } + let { Ah, Al, Bh, Bl, Ch, Cl, Dh, Dl, Eh, El, Fh, Fl, Gh, Gl, Hh, Hl } = this; + for (let i2 = 0; i2 < 80; i2++) { + const sigma1h = u64_default.rotrSH(Eh, El, 14) ^ u64_default.rotrSH(Eh, El, 18) ^ u64_default.rotrBH(Eh, El, 41); + const sigma1l = u64_default.rotrSL(Eh, El, 14) ^ u64_default.rotrSL(Eh, El, 18) ^ u64_default.rotrBL(Eh, El, 41); + const CHIh = Eh & Fh ^ ~Eh & Gh; + const CHIl = El & Fl ^ ~El & Gl; + const T1ll = u64_default.add5L(Hl, sigma1l, CHIl, SHA512_Kl[i2], SHA512_W_L[i2]); + const T1h = u64_default.add5H(T1ll, Hh, sigma1h, CHIh, SHA512_Kh[i2], SHA512_W_H[i2]); + const T1l = T1ll | 0; + const sigma0h = u64_default.rotrSH(Ah, Al, 28) ^ u64_default.rotrBH(Ah, Al, 34) ^ u64_default.rotrBH(Ah, Al, 39); + const sigma0l = u64_default.rotrSL(Ah, Al, 28) ^ u64_default.rotrBL(Ah, Al, 34) ^ u64_default.rotrBL(Ah, Al, 39); + const MAJh = Ah & Bh ^ Ah & Ch ^ Bh & Ch; + const MAJl = Al & Bl ^ Al & Cl ^ Bl & Cl; + Hh = Gh | 0; + Hl = Gl | 0; + Gh = Fh | 0; + Gl = Fl | 0; + Fh = Eh | 0; + Fl = El | 0; + ({ h: Eh, l: El } = u64_default.add(Dh | 0, Dl | 0, T1h | 0, T1l | 0)); + Dh = Ch | 0; + Dl = Cl | 0; + Ch = Bh | 0; + Cl = Bl | 0; + Bh = Ah | 0; + Bl = Al | 0; + const All = u64_default.add3L(T1l, sigma0l, MAJl); + Ah = u64_default.add3H(All, T1h, sigma0h, MAJh); + Al = All | 0; + } + ({ h: Ah, l: Al } = u64_default.add(this.Ah | 0, this.Al | 0, Ah | 0, Al | 0)); + ({ h: Bh, l: Bl } = u64_default.add(this.Bh | 0, this.Bl | 0, Bh | 0, Bl | 0)); + ({ h: Ch, l: Cl } = u64_default.add(this.Ch | 0, this.Cl | 0, Ch | 0, Cl | 0)); + ({ h: Dh, l: Dl } = u64_default.add(this.Dh | 0, this.Dl | 0, Dh | 0, Dl | 0)); + ({ h: Eh, l: El } = u64_default.add(this.Eh | 0, this.El | 0, Eh | 0, El | 0)); + ({ h: Fh, l: Fl } = u64_default.add(this.Fh | 0, this.Fl | 0, Fh | 0, Fl | 0)); + ({ h: Gh, l: Gl } = u64_default.add(this.Gh | 0, this.Gl | 0, Gh | 0, Gl | 0)); + ({ h: Hh, l: Hl } = u64_default.add(this.Hh | 0, this.Hl | 0, Hh | 0, Hl | 0)); + this.set(Ah, Al, Bh, Bl, Ch, Cl, Dh, Dl, Eh, El, Fh, Fl, Gh, Gl, Hh, Hl); + } + roundClean() { + SHA512_W_H.fill(0); + SHA512_W_L.fill(0); + } + destroy() { + this.buffer.fill(0); + this.set(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); + } + }; + var SHA512_224 = class extends SHA512 { + constructor() { + super(); + this.Ah = 2352822216 | 0; + this.Al = 424955298 | 0; + this.Bh = 1944164710 | 0; + this.Bl = 2312950998 | 0; + this.Ch = 502970286 | 0; + this.Cl = 855612546 | 0; + this.Dh = 1738396948 | 0; + this.Dl = 1479516111 | 0; + this.Eh = 258812777 | 0; + this.El = 2077511080 | 0; + this.Fh = 2011393907 | 0; + this.Fl = 79989058 | 0; + this.Gh = 1067287976 | 0; + this.Gl = 1780299464 | 0; + this.Hh = 286451373 | 0; + this.Hl = 2446758561 | 0; + this.outputLen = 28; + } + }; + var SHA512_256 = class extends SHA512 { + constructor() { + super(); + this.Ah = 573645204 | 0; + this.Al = 4230739756 | 0; + this.Bh = 2673172387 | 0; + this.Bl = 3360449730 | 0; + this.Ch = 596883563 | 0; + this.Cl = 1867755857 | 0; + this.Dh = 2520282905 | 0; + this.Dl = 1497426621 | 0; + this.Eh = 2519219938 | 0; + this.El = 2827943907 | 0; + this.Fh = 3193839141 | 0; + this.Fl = 1401305490 | 0; + this.Gh = 721525244 | 0; + this.Gl = 746961066 | 0; + this.Hh = 246885852 | 0; + this.Hl = 2177182882 | 0; + this.outputLen = 32; + } + }; + var SHA384 = class extends SHA512 { + constructor() { + super(); + this.Ah = 3418070365 | 0; + this.Al = 3238371032 | 0; + this.Bh = 1654270250 | 0; + this.Bl = 914150663 | 0; + this.Ch = 2438529370 | 0; + this.Cl = 812702999 | 0; + this.Dh = 355462360 | 0; + this.Dl = 4144912697 | 0; + this.Eh = 1731405415 | 0; + this.El = 4290775857 | 0; + this.Fh = 2394180231 | 0; + this.Fl = 1750603025 | 0; + this.Gh = 3675008525 | 0; + this.Gl = 1694076839 | 0; + this.Hh = 1203062813 | 0; + this.Hl = 3204075428 | 0; + this.outputLen = 48; + } + }; + var sha512 = wrapConstructor2(() => new SHA512()); + var sha512_224 = wrapConstructor2(() => new SHA512_224()); + var sha512_256 = wrapConstructor2(() => new SHA512_256()); + var sha384 = wrapConstructor2(() => new SHA384()); + + // node_modules/@scure/bip39/esm/index.js + var isJapanese = (wordlist2) => wordlist2[0] === "\u3042\u3044\u3053\u304F\u3057\u3093"; + function nfkd(str) { + if (typeof str !== "string") + throw new TypeError(`Invalid mnemonic type: ${typeof str}`); + return str.normalize("NFKD"); + } + function normalize2(str) { + const norm = nfkd(str); + const words = norm.split(" "); + if (![12, 15, 18, 21, 24].includes(words.length)) + throw new Error("Invalid mnemonic"); + return { nfkd: norm, words }; + } + function assertEntropy(entropy) { + assert_default.bytes(entropy, 16, 20, 24, 28, 32); + } + function generateMnemonic(wordlist2, strength = 128) { + assert_default.number(strength); + if (strength % 32 !== 0 || strength > 256) + throw new TypeError("Invalid entropy"); + return entropyToMnemonic(randomBytes2(strength / 8), wordlist2); + } + var calcChecksum = (entropy) => { + const bitsLeft = 8 - entropy.length / 4; + return new Uint8Array([sha2562(entropy)[0] >> bitsLeft << bitsLeft]); + }; + function getCoder(wordlist2) { + if (!Array.isArray(wordlist2) || wordlist2.length !== 2048 || typeof wordlist2[0] !== "string") + throw new Error("Worlist: expected array of 2048 strings"); + wordlist2.forEach((i2) => { + if (typeof i2 !== "string") + throw new Error(`Wordlist: non-string element: ${i2}`); + }); + return utils.chain(utils.checksum(1, calcChecksum), utils.radix2(11, true), utils.alphabet(wordlist2)); + } + function mnemonicToEntropy(mnemonic, wordlist2) { + const { words } = normalize2(mnemonic); + const entropy = getCoder(wordlist2).decode(words); + assertEntropy(entropy); + return entropy; + } + function entropyToMnemonic(entropy, wordlist2) { + assertEntropy(entropy); + const words = getCoder(wordlist2).encode(entropy); + return words.join(isJapanese(wordlist2) ? "\u3000" : " "); + } + function validateMnemonic(mnemonic, wordlist2) { + try { + mnemonicToEntropy(mnemonic, wordlist2); + } catch (e) { + return false; + } + return true; + } + var salt = (passphrase) => nfkd(`mnemonic${passphrase}`); + function mnemonicToSeedSync(mnemonic, passphrase = "") { + return pbkdf2(sha512, normalize2(mnemonic).nfkd, salt(passphrase), { c: 2048, dkLen: 64 }); + } + + // node_modules/@noble/hashes/esm/ripemd160.js + var Rho = new Uint8Array([7, 4, 13, 1, 10, 6, 15, 3, 12, 0, 9, 5, 2, 14, 11, 8]); + var Id = Uint8Array.from({ length: 16 }, (_, i2) => i2); + var Pi = Id.map((i2) => (9 * i2 + 5) % 16); + var idxL = [Id]; + var idxR = [Pi]; + for (let i2 = 0; i2 < 4; i2++) + for (let j of [idxL, idxR]) + j.push(j[i2].map((k) => Rho[k])); + var shifts = [ + [11, 14, 15, 12, 5, 8, 7, 9, 11, 13, 14, 15, 6, 7, 9, 8], + [12, 13, 11, 15, 6, 9, 9, 7, 12, 15, 11, 13, 7, 8, 7, 7], + [13, 15, 14, 11, 7, 7, 6, 8, 13, 14, 13, 12, 5, 5, 6, 9], + [14, 11, 12, 14, 8, 6, 5, 5, 15, 12, 15, 14, 9, 9, 8, 6], + [15, 12, 13, 13, 9, 5, 8, 6, 14, 11, 12, 11, 8, 6, 5, 5] + ].map((i2) => new Uint8Array(i2)); + var shiftsL = idxL.map((idx, i2) => idx.map((j) => shifts[i2][j])); + var shiftsR = idxR.map((idx, i2) => idx.map((j) => shifts[i2][j])); + var Kl = new Uint32Array([0, 1518500249, 1859775393, 2400959708, 2840853838]); + var Kr = new Uint32Array([1352829926, 1548603684, 1836072691, 2053994217, 0]); + var rotl = (word, shift) => word << shift | word >>> 32 - shift; + function f(group, x, y, z) { + if (group === 0) + return x ^ y ^ z; + else if (group === 1) + return x & y | ~x & z; + else if (group === 2) + return (x | ~y) ^ z; + else if (group === 3) + return x & z | y & ~z; + else + return x ^ (y | ~z); + } + var BUF = new Uint32Array(16); + var RIPEMD160 = class extends SHA22 { + constructor() { + super(64, 20, 8, true); + this.h0 = 1732584193 | 0; + this.h1 = 4023233417 | 0; + this.h2 = 2562383102 | 0; + this.h3 = 271733878 | 0; + this.h4 = 3285377520 | 0; + } + get() { + const { h0, h1, h2, h3, h4 } = this; + return [h0, h1, h2, h3, h4]; + } + set(h0, h1, h2, h3, h4) { + this.h0 = h0 | 0; + this.h1 = h1 | 0; + this.h2 = h2 | 0; + this.h3 = h3 | 0; + this.h4 = h4 | 0; + } + process(view, offset) { + for (let i2 = 0; i2 < 16; i2++, offset += 4) + BUF[i2] = view.getUint32(offset, true); + let al = this.h0 | 0, ar = al, bl = this.h1 | 0, br = bl, cl = this.h2 | 0, cr = cl, dl = this.h3 | 0, dr = dl, el = this.h4 | 0, er = el; + for (let group = 0; group < 5; group++) { + const rGroup = 4 - group; + const hbl = Kl[group], hbr = Kr[group]; + const rl = idxL[group], rr = idxR[group]; + const sl = shiftsL[group], sr = shiftsR[group]; + for (let i2 = 0; i2 < 16; i2++) { + const tl = rotl(al + f(group, bl, cl, dl) + BUF[rl[i2]] + hbl, sl[i2]) + el | 0; + al = el, el = dl, dl = rotl(cl, 10) | 0, cl = bl, bl = tl; + } + for (let i2 = 0; i2 < 16; i2++) { + const tr = rotl(ar + f(rGroup, br, cr, dr) + BUF[rr[i2]] + hbr, sr[i2]) + er | 0; + ar = er, er = dr, dr = rotl(cr, 10) | 0, cr = br, br = tr; + } + } + this.set(this.h1 + cl + dr | 0, this.h2 + dl + er | 0, this.h3 + el + ar | 0, this.h4 + al + br | 0, this.h0 + bl + cr | 0); + } + roundClean() { + BUF.fill(0); + } + destroy() { + this.destroyed = true; + this.buffer.fill(0); + this.set(0, 0, 0, 0, 0); + } + }; + var ripemd160 = wrapConstructor2(() => new RIPEMD160()); + + // node_modules/@scure/bip32/node_modules/@noble/curves/esm/abstract/utils.js + var utils_exports3 = {}; + __export(utils_exports3, { + bitGet: () => bitGet2, + bitLen: () => bitLen2, + bitMask: () => bitMask2, + bitSet: () => bitSet2, + bytesToHex: () => bytesToHex3, + bytesToNumberBE: () => bytesToNumberBE2, + bytesToNumberLE: () => bytesToNumberLE2, + concatBytes: () => concatBytes4, + createHmacDrbg: () => createHmacDrbg2, + ensureBytes: () => ensureBytes2, + equalBytes: () => equalBytes3, + hexToBytes: () => hexToBytes3, + hexToNumber: () => hexToNumber2, + numberToBytesBE: () => numberToBytesBE2, + numberToBytesLE: () => numberToBytesLE2, + numberToHexUnpadded: () => numberToHexUnpadded2, + numberToVarBytesBE: () => numberToVarBytesBE2, + utf8ToBytes: () => utf8ToBytes5, + validateObject: () => validateObject2 + }); + var _0n6 = BigInt(0); + var _1n6 = BigInt(1); + var _2n5 = BigInt(2); + var u8a4 = (a) => a instanceof Uint8Array; + var hexes3 = Array.from({ length: 256 }, (v, i2) => i2.toString(16).padStart(2, "0")); + function bytesToHex3(bytes4) { + if (!u8a4(bytes4)) + throw new Error("Uint8Array expected"); + let hex2 = ""; + for (let i2 = 0; i2 < bytes4.length; i2++) { + hex2 += hexes3[bytes4[i2]]; + } + return hex2; + } + function numberToHexUnpadded2(num) { + const hex2 = num.toString(16); + return hex2.length & 1 ? `0${hex2}` : hex2; + } + function hexToNumber2(hex2) { + if (typeof hex2 !== "string") + throw new Error("hex string expected, got " + typeof hex2); + return BigInt(hex2 === "" ? "0" : `0x${hex2}`); + } + function hexToBytes3(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 bytesToNumberBE2(bytes4) { + return hexToNumber2(bytesToHex3(bytes4)); + } + function bytesToNumberLE2(bytes4) { + if (!u8a4(bytes4)) + throw new Error("Uint8Array expected"); + return hexToNumber2(bytesToHex3(Uint8Array.from(bytes4).reverse())); + } + function numberToBytesBE2(n, len) { + return hexToBytes3(n.toString(16).padStart(len * 2, "0")); + } + function numberToBytesLE2(n, len) { + return numberToBytesBE2(n, len).reverse(); + } + function numberToVarBytesBE2(n) { + return hexToBytes3(numberToHexUnpadded2(n)); + } + function ensureBytes2(title, hex2, expectedLength) { + let res; + if (typeof hex2 === "string") { + try { + res = hexToBytes3(hex2); + } catch (e) { + throw new Error(`${title} must be valid hex string, got "${hex2}". Cause: ${e}`); + } + } else if (u8a4(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 concatBytes4(...arrays) { + const r = new Uint8Array(arrays.reduce((sum, a) => sum + a.length, 0)); + let pad2 = 0; + arrays.forEach((a) => { + if (!u8a4(a)) + throw new Error("Uint8Array expected"); + r.set(a, pad2); + pad2 += a.length; + }); + return r; + } + function equalBytes3(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 utf8ToBytes5(str) { + if (typeof str !== "string") + throw new Error(`utf8ToBytes expected string, got ${typeof str}`); + return new Uint8Array(new TextEncoder().encode(str)); + } + function bitLen2(n) { + let len; + for (len = 0; n > _0n6; n >>= _1n6, len += 1) + ; + return len; + } + function bitGet2(n, pos) { + return n >> BigInt(pos) & _1n6; + } + var bitSet2 = (n, pos, value) => { + return n | (value ? _1n6 : _0n6) << BigInt(pos); + }; + var bitMask2 = (n) => (_2n5 << BigInt(n - 1)) - _1n6; + var u8n2 = (data) => new Uint8Array(data); + var u8fr2 = (arr) => Uint8Array.from(arr); + function createHmacDrbg2(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 = u8n2(hashLen); + let k = u8n2(hashLen); + let i2 = 0; + const reset = () => { + v.fill(1); + k.fill(0); + i2 = 0; + }; + const h = (...b) => hmacFn(k, v, ...b); + const reseed = (seed = u8n2()) => { + k = h(u8fr2([0]), seed); + v = h(); + if (seed.length === 0) + return; + k = h(u8fr2([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 concatBytes4(...out); + }; + const genUntil = (seed, pred) => { + reset(); + reseed(seed); + let res = void 0; + while (!(res = pred(gen()))) + reseed(); + reset(); + return res; + }; + return genUntil; + } + var validatorFns2 = { + bigint: (val) => typeof val === "bigint", + function: (val) => typeof val === "function", + boolean: (val) => typeof val === "boolean", + string: (val) => typeof val === "string", + 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 validateObject2(object, validators, optValidators = {}) { + const checkField = (fieldName, type, isOptional) => { + const checkVal = validatorFns2[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/@scure/bip32/node_modules/@noble/curves/esm/abstract/modular.js + var _0n7 = BigInt(0); + var _1n7 = BigInt(1); + var _2n6 = BigInt(2); + var _3n3 = BigInt(3); + var _4n3 = BigInt(4); + var _5n2 = BigInt(5); + var _8n2 = BigInt(8); + var _9n2 = BigInt(9); + var _16n2 = BigInt(16); + function mod2(a, b) { + const result = a % b; + return result >= _0n7 ? result : b + result; + } + function pow3(num, power, modulo) { + if (modulo <= _0n7 || power < _0n7) + throw new Error("Expected power/modulo > 0"); + if (modulo === _1n7) + return _0n7; + let res = _1n7; + while (power > _0n7) { + if (power & _1n7) + res = res * num % modulo; + num = num * num % modulo; + power >>= _1n7; + } + return res; + } + function pow22(x, power, modulo) { + let res = x; + while (power-- > _0n7) { + res *= res; + res %= modulo; + } + return res; + } + function invert2(number4, modulo) { + if (number4 === _0n7 || modulo <= _0n7) { + throw new Error(`invert: expected positive integers, got n=${number4} mod=${modulo}`); + } + let a = mod2(number4, modulo); + let b = modulo; + let x = _0n7, y = _1n7, u = _1n7, v = _0n7; + while (a !== _0n7) { + 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 !== _1n7) + throw new Error("invert: does not exist"); + return mod2(x, modulo); + } + function tonelliShanks2(P) { + const legendreC = (P - _1n7) / _2n6; + let Q, S, Z; + for (Q = P - _1n7, S = 0; Q % _2n6 === _0n7; Q /= _2n6, S++) + ; + for (Z = _2n6; Z < P && pow3(Z, legendreC, P) !== P - _1n7; Z++) + ; + if (S === 1) { + const p1div4 = (P + _1n7) / _4n3; + return function tonelliFast(Fp3, n) { + const root = Fp3.pow(n, p1div4); + if (!Fp3.eql(Fp3.sqr(root), n)) + throw new Error("Cannot find square root"); + return root; + }; + } + const Q1div2 = (Q + _1n7) / _2n6; + return function tonelliSlow(Fp3, n) { + if (Fp3.pow(n, legendreC) === Fp3.neg(Fp3.ONE)) + throw new Error("Cannot find square root"); + let r = S; + let g = Fp3.pow(Fp3.mul(Fp3.ONE, Z), Q); + let x = Fp3.pow(n, Q1div2); + let b = Fp3.pow(n, Q); + while (!Fp3.eql(b, Fp3.ONE)) { + if (Fp3.eql(b, Fp3.ZERO)) + return Fp3.ZERO; + let m = 1; + for (let t2 = Fp3.sqr(b); m < r; m++) { + if (Fp3.eql(t2, Fp3.ONE)) + break; + t2 = Fp3.sqr(t2); + } + const ge2 = Fp3.pow(g, _1n7 << BigInt(r - m - 1)); + g = Fp3.sqr(ge2); + x = Fp3.mul(x, ge2); + b = Fp3.mul(b, g); + r = m; + } + return x; + }; + } + function FpSqrt2(P) { + if (P % _4n3 === _3n3) { + const p1div4 = (P + _1n7) / _4n3; + return function sqrt3mod4(Fp3, n) { + const root = Fp3.pow(n, p1div4); + if (!Fp3.eql(Fp3.sqr(root), n)) + throw new Error("Cannot find square root"); + return root; + }; + } + if (P % _8n2 === _5n2) { + const c1 = (P - _5n2) / _8n2; + return function sqrt5mod8(Fp3, n) { + const n2 = Fp3.mul(n, _2n6); + const v = Fp3.pow(n2, c1); + const nv = Fp3.mul(n, v); + const i2 = Fp3.mul(Fp3.mul(nv, _2n6), v); + const root = Fp3.mul(nv, Fp3.sub(i2, Fp3.ONE)); + if (!Fp3.eql(Fp3.sqr(root), n)) + throw new Error("Cannot find square root"); + return root; + }; + } + if (P % _16n2 === _9n2) { + } + return tonelliShanks2(P); + } + var FIELD_FIELDS2 = [ + "create", + "isValid", + "is0", + "neg", + "inv", + "sqrt", + "sqr", + "eql", + "add", + "sub", + "mul", + "pow", + "div", + "addN", + "subN", + "mulN", + "sqrN" + ]; + function validateField2(field) { + const initial = { + ORDER: "bigint", + MASK: "bigint", + BYTES: "isSafeInteger", + BITS: "isSafeInteger" + }; + const opts = FIELD_FIELDS2.reduce((map, val) => { + map[val] = "function"; + return map; + }, initial); + return validateObject2(field, opts); + } + function FpPow2(f2, num, power) { + if (power < _0n7) + throw new Error("Expected power > 0"); + if (power === _0n7) + return f2.ONE; + if (power === _1n7) + return num; + let p = f2.ONE; + let d = num; + while (power > _0n7) { + if (power & _1n7) + p = f2.mul(p, d); + d = f2.sqr(d); + power >>= _1n7; + } + return p; + } + function FpInvertBatch2(f2, nums) { + const tmp = new Array(nums.length); + const lastMultiplied = nums.reduce((acc, num, i2) => { + if (f2.is0(num)) + return acc; + tmp[i2] = acc; + return f2.mul(acc, num); + }, f2.ONE); + const inverted = f2.inv(lastMultiplied); + nums.reduceRight((acc, num, i2) => { + if (f2.is0(num)) + return acc; + tmp[i2] = f2.mul(acc, tmp[i2]); + return f2.mul(acc, num); + }, inverted); + return tmp; + } + function nLength2(n, nBitLength) { + const _nBitLength = nBitLength !== void 0 ? nBitLength : n.toString(2).length; + const nByteLength = Math.ceil(_nBitLength / 8); + return { nBitLength: _nBitLength, nByteLength }; + } + function Field2(ORDER, bitLen3, isLE4 = false, redef = {}) { + if (ORDER <= _0n7) + throw new Error(`Expected Fp ORDER > 0, got ${ORDER}`); + const { nBitLength: BITS, nByteLength: BYTES } = nLength2(ORDER, bitLen3); + if (BYTES > 2048) + throw new Error("Field lengths over 2048 bytes are not supported"); + const sqrtP = FpSqrt2(ORDER); + const f2 = Object.freeze({ + ORDER, + BITS, + BYTES, + MASK: bitMask2(BITS), + ZERO: _0n7, + ONE: _1n7, + create: (num) => mod2(num, ORDER), + isValid: (num) => { + if (typeof num !== "bigint") + throw new Error(`Invalid field element: expected bigint, got ${typeof num}`); + return _0n7 <= num && num < ORDER; + }, + is0: (num) => num === _0n7, + isOdd: (num) => (num & _1n7) === _1n7, + neg: (num) => mod2(-num, ORDER), + eql: (lhs, rhs) => lhs === rhs, + sqr: (num) => mod2(num * num, ORDER), + add: (lhs, rhs) => mod2(lhs + rhs, ORDER), + sub: (lhs, rhs) => mod2(lhs - rhs, ORDER), + mul: (lhs, rhs) => mod2(lhs * rhs, ORDER), + pow: (num, power) => FpPow2(f2, num, power), + div: (lhs, rhs) => mod2(lhs * invert2(rhs, ORDER), ORDER), + sqrN: (num) => num * num, + addN: (lhs, rhs) => lhs + rhs, + subN: (lhs, rhs) => lhs - rhs, + mulN: (lhs, rhs) => lhs * rhs, + inv: (num) => invert2(num, ORDER), + sqrt: redef.sqrt || ((n) => sqrtP(f2, n)), + invertBatch: (lst) => FpInvertBatch2(f2, lst), + cmov: (a, b, c) => c ? b : a, + toBytes: (num) => isLE4 ? numberToBytesLE2(num, BYTES) : numberToBytesBE2(num, BYTES), + fromBytes: (bytes4) => { + if (bytes4.length !== BYTES) + throw new Error(`Fp.fromBytes: expected ${BYTES}, got ${bytes4.length}`); + return isLE4 ? bytesToNumberLE2(bytes4) : bytesToNumberBE2(bytes4); + } + }); + return Object.freeze(f2); + } + function hashToPrivateScalar(hash3, groupOrder, isLE4 = false) { + hash3 = ensureBytes2("privateHash", hash3); + const hashLen = hash3.length; + const minLen = nLength2(groupOrder).nByteLength + 8; + if (minLen < 24 || hashLen < minLen || hashLen > 1024) + throw new Error(`hashToPrivateScalar: expected ${minLen}-1024 bytes of input, got ${hashLen}`); + const num = isLE4 ? bytesToNumberLE2(hash3) : bytesToNumberBE2(hash3); + return mod2(num, groupOrder - _1n7) + _1n7; + } + + // node_modules/@scure/bip32/node_modules/@noble/curves/esm/abstract/curve.js + var _0n8 = BigInt(0); + var _1n8 = BigInt(1); + function wNAF2(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 > _0n8) { + if (n & _1n8) + p = p.add(d); + d = d.double(); + n >>= _1n8; + } + 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 f2 = 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 += _1n8; + } + const offset1 = offset; + const offset2 = offset + Math.abs(wbits) - 1; + const cond1 = window % 2 !== 0; + const cond2 = wbits < 0; + if (wbits === 0) { + f2 = f2.add(constTimeNegate(cond1, precomputes[offset1])); + } else { + p = p.add(constTimeNegate(cond2, precomputes[offset2])); + } + } + return { p, f: f2 }; + }, + 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 validateBasic2(curve) { + validateField2(curve.Fp); + validateObject2(curve, { + n: "bigint", + h: "bigint", + Gx: "field", + Gy: "field" + }, { + nBitLength: "isSafeInteger", + nByteLength: "isSafeInteger" + }); + return Object.freeze({ + ...nLength2(curve.n, curve.nBitLength), + ...curve, + ...{ p: curve.Fp.ORDER } + }); + } + + // node_modules/@scure/bip32/node_modules/@noble/curves/esm/abstract/weierstrass.js + function validatePointOpts2(curve) { + const opts = validateBasic2(curve); + validateObject2(opts, { + a: "field", + b: "field" + }, { + allowedPrivateKeyLengths: "array", + wrapPrivateKey: "boolean", + isTorsionFree: "function", + clearCofactor: "function", + allowInfinityPoint: "boolean", + fromBytes: "function", + toBytes: "function" + }); + const { endo, Fp: Fp3, a } = opts; + if (endo) { + if (!Fp3.eql(a, Fp3.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: b2n2, hexToBytes: h2b2 } = utils_exports3; + var DER2 = { + Err: class DERErr2 extends Error { + constructor(m = "") { + super(m); + } + }, + _parseInt(data) { + const { Err: E } = DER2; + 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: b2n2(res), l: data.subarray(len + 2) }; + }, + toSig(hex2) { + const { Err: E } = DER2; + const data = typeof hex2 === "string" ? h2b2(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 } = DER2._parseInt(data.subarray(2)); + const { d: s, l: rBytesLeft } = DER2._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 _0n9 = BigInt(0); + var _1n9 = BigInt(1); + var _2n7 = BigInt(2); + var _3n4 = BigInt(3); + var _4n4 = BigInt(4); + function weierstrassPoints2(opts) { + const CURVE = validatePointOpts2(opts); + const { Fp: Fp3 } = CURVE; + const toBytes4 = CURVE.toBytes || ((c, point, isCompressed) => { + const a = point.toAffine(); + return concatBytes4(Uint8Array.from([4]), Fp3.toBytes(a.x), Fp3.toBytes(a.y)); + }); + const fromBytes = CURVE.fromBytes || ((bytes4) => { + const tail = bytes4.subarray(1); + const x = Fp3.fromBytes(tail.subarray(0, Fp3.BYTES)); + const y = Fp3.fromBytes(tail.subarray(Fp3.BYTES, 2 * Fp3.BYTES)); + return { x, y }; + }); + function weierstrassEquation(x) { + const { a, b } = CURVE; + const x2 = Fp3.sqr(x); + const x3 = Fp3.mul(x2, x); + return Fp3.add(Fp3.add(x3, Fp3.mul(x, a)), b); + } + if (!Fp3.eql(Fp3.sqr(CURVE.Gy), weierstrassEquation(CURVE.Gx))) + throw new Error("bad generator point: equation left != right"); + function isWithinCurveOrder(num) { + return typeof num === "bigint" && _0n9 < 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 = bytesToHex3(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 : bytesToNumberBE2(ensureBytes2("private key", key, nByteLength)); + } catch (error) { + throw new Error(`private key must be ${nByteLength} bytes, hex or bigint, not ${typeof key}`); + } + if (wrapPrivateKey) + num = mod2(num, n); + assertGE(num); + return num; + } + const pointPrecomputes = /* @__PURE__ */ new Map(); + function assertPrjPoint(other) { + if (!(other instanceof Point4)) + throw new Error("ProjectivePoint expected"); + } + class Point4 { + constructor(px, py, pz) { + this.px = px; + this.py = py; + this.pz = pz; + if (px == null || !Fp3.isValid(px)) + throw new Error("x required"); + if (py == null || !Fp3.isValid(py)) + throw new Error("y required"); + if (pz == null || !Fp3.isValid(pz)) + throw new Error("z required"); + } + static fromAffine(p) { + const { x, y } = p || {}; + if (!p || !Fp3.isValid(x) || !Fp3.isValid(y)) + throw new Error("invalid affine point"); + if (p instanceof Point4) + throw new Error("projective point not allowed"); + const is0 = (i2) => Fp3.eql(i2, Fp3.ZERO); + if (is0(x) && is0(y)) + return Point4.ZERO; + return new Point4(x, y, Fp3.ONE); + } + get x() { + return this.toAffine().x; + } + get y() { + return this.toAffine().y; + } + static normalizeZ(points) { + const toInv = Fp3.invertBatch(points.map((p) => p.pz)); + return points.map((p, i2) => p.toAffine(toInv[i2])).map(Point4.fromAffine); + } + static fromHex(hex2) { + const P = Point4.fromAffine(fromBytes(ensureBytes2("pointHex", hex2))); + P.assertValidity(); + return P; + } + static fromPrivateKey(privateKey) { + return Point4.BASE.multiply(normPrivateKeyToScalar(privateKey)); + } + _setWindowSize(windowSize) { + this._WINDOW_SIZE = windowSize; + pointPrecomputes.delete(this); + } + assertValidity() { + if (this.is0()) { + if (CURVE.allowInfinityPoint) + return; + throw new Error("bad point: ZERO"); + } + const { x, y } = this.toAffine(); + if (!Fp3.isValid(x) || !Fp3.isValid(y)) + throw new Error("bad point: x or y not FE"); + const left = Fp3.sqr(y); + const right = weierstrassEquation(x); + if (!Fp3.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 (Fp3.isOdd) + return !Fp3.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 = Fp3.eql(Fp3.mul(X1, Z2), Fp3.mul(X2, Z1)); + const U2 = Fp3.eql(Fp3.mul(Y1, Z2), Fp3.mul(Y2, Z1)); + return U1 && U2; + } + negate() { + return new Point4(this.px, Fp3.neg(this.py), this.pz); + } + double() { + const { a, b } = CURVE; + const b3 = Fp3.mul(b, _3n4); + const { px: X1, py: Y1, pz: Z1 } = this; + let X3 = Fp3.ZERO, Y3 = Fp3.ZERO, Z3 = Fp3.ZERO; + let t0 = Fp3.mul(X1, X1); + let t1 = Fp3.mul(Y1, Y1); + let t2 = Fp3.mul(Z1, Z1); + let t3 = Fp3.mul(X1, Y1); + t3 = Fp3.add(t3, t3); + Z3 = Fp3.mul(X1, Z1); + Z3 = Fp3.add(Z3, Z3); + X3 = Fp3.mul(a, Z3); + Y3 = Fp3.mul(b3, t2); + Y3 = Fp3.add(X3, Y3); + X3 = Fp3.sub(t1, Y3); + Y3 = Fp3.add(t1, Y3); + Y3 = Fp3.mul(X3, Y3); + X3 = Fp3.mul(t3, X3); + Z3 = Fp3.mul(b3, Z3); + t2 = Fp3.mul(a, t2); + t3 = Fp3.sub(t0, t2); + t3 = Fp3.mul(a, t3); + t3 = Fp3.add(t3, Z3); + Z3 = Fp3.add(t0, t0); + t0 = Fp3.add(Z3, t0); + t0 = Fp3.add(t0, t2); + t0 = Fp3.mul(t0, t3); + Y3 = Fp3.add(Y3, t0); + t2 = Fp3.mul(Y1, Z1); + t2 = Fp3.add(t2, t2); + t0 = Fp3.mul(t2, t3); + X3 = Fp3.sub(X3, t0); + Z3 = Fp3.mul(t2, t1); + Z3 = Fp3.add(Z3, Z3); + Z3 = Fp3.add(Z3, Z3); + return new Point4(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 = Fp3.ZERO, Y3 = Fp3.ZERO, Z3 = Fp3.ZERO; + const a = CURVE.a; + const b3 = Fp3.mul(CURVE.b, _3n4); + let t0 = Fp3.mul(X1, X2); + let t1 = Fp3.mul(Y1, Y2); + let t2 = Fp3.mul(Z1, Z2); + let t3 = Fp3.add(X1, Y1); + let t4 = Fp3.add(X2, Y2); + t3 = Fp3.mul(t3, t4); + t4 = Fp3.add(t0, t1); + t3 = Fp3.sub(t3, t4); + t4 = Fp3.add(X1, Z1); + let t5 = Fp3.add(X2, Z2); + t4 = Fp3.mul(t4, t5); + t5 = Fp3.add(t0, t2); + t4 = Fp3.sub(t4, t5); + t5 = Fp3.add(Y1, Z1); + X3 = Fp3.add(Y2, Z2); + t5 = Fp3.mul(t5, X3); + X3 = Fp3.add(t1, t2); + t5 = Fp3.sub(t5, X3); + Z3 = Fp3.mul(a, t4); + X3 = Fp3.mul(b3, t2); + Z3 = Fp3.add(X3, Z3); + X3 = Fp3.sub(t1, Z3); + Z3 = Fp3.add(t1, Z3); + Y3 = Fp3.mul(X3, Z3); + t1 = Fp3.add(t0, t0); + t1 = Fp3.add(t1, t0); + t2 = Fp3.mul(a, t2); + t4 = Fp3.mul(b3, t4); + t1 = Fp3.add(t1, t2); + t2 = Fp3.sub(t0, t2); + t2 = Fp3.mul(a, t2); + t4 = Fp3.add(t4, t2); + t0 = Fp3.mul(t1, t4); + Y3 = Fp3.add(Y3, t0); + t0 = Fp3.mul(t5, t4); + X3 = Fp3.mul(t3, X3); + X3 = Fp3.sub(X3, t0); + t0 = Fp3.mul(t3, t1); + Z3 = Fp3.mul(t5, Z3); + Z3 = Fp3.add(Z3, t0); + return new Point4(X3, Y3, Z3); + } + subtract(other) { + return this.add(other.negate()); + } + is0() { + return this.equals(Point4.ZERO); + } + wNAF(n) { + return wnaf.wNAFCached(this, pointPrecomputes, n, (comp) => { + const toInv = Fp3.invertBatch(comp.map((p) => p.pz)); + return comp.map((p, i2) => p.toAffine(toInv[i2])).map(Point4.fromAffine); + }); + } + multiplyUnsafe(n) { + const I = Point4.ZERO; + if (n === _0n9) + return I; + assertGE(n); + if (n === _1n9) + 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 > _0n9 || k2 > _0n9) { + if (k1 & _1n9) + k1p = k1p.add(d); + if (k2 & _1n9) + k2p = k2p.add(d); + d = d.double(); + k1 >>= _1n9; + k2 >>= _1n9; + } + if (k1neg) + k1p = k1p.negate(); + if (k2neg) + k2p = k2p.negate(); + k2p = new Point4(Fp3.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 Point4(Fp3.mul(k2p.px, endo.beta), k2p.py, k2p.pz); + point = k1p.add(k2p); + fake = f1p.add(f2p); + } else { + const { p, f: f2 } = this.wNAF(n); + point = p; + fake = f2; + } + return Point4.normalizeZ([point, fake])[0]; + } + multiplyAndAddUnsafe(Q, a, b) { + const G = Point4.BASE; + const mul3 = (P, a2) => a2 === _0n9 || a2 === _1n9 || !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 ? Fp3.ONE : Fp3.inv(z); + const ax = Fp3.mul(x, iz); + const ay = Fp3.mul(y, iz); + const zz = Fp3.mul(z, iz); + if (is0) + return { x: Fp3.ZERO, y: Fp3.ZERO }; + if (!Fp3.eql(zz, Fp3.ONE)) + throw new Error("invZ was invalid"); + return { x: ax, y: ay }; + } + isTorsionFree() { + const { h: cofactor, isTorsionFree } = CURVE; + if (cofactor === _1n9) + return true; + if (isTorsionFree) + return isTorsionFree(Point4, this); + throw new Error("isTorsionFree() has not been declared for the elliptic curve"); + } + clearCofactor() { + const { h: cofactor, clearCofactor } = CURVE; + if (cofactor === _1n9) + return this; + if (clearCofactor) + return clearCofactor(Point4, this); + return this.multiplyUnsafe(CURVE.h); + } + toRawBytes(isCompressed = true) { + this.assertValidity(); + return toBytes4(Point4, this, isCompressed); + } + toHex(isCompressed = true) { + return bytesToHex3(this.toRawBytes(isCompressed)); + } + } + Point4.BASE = new Point4(CURVE.Gx, CURVE.Gy, Fp3.ONE); + Point4.ZERO = new Point4(Fp3.ZERO, Fp3.ONE, Fp3.ZERO); + const _bits = CURVE.nBitLength; + const wnaf = wNAF2(Point4, CURVE.endo ? Math.ceil(_bits / 2) : _bits); + return { + CURVE, + ProjectivePoint: Point4, + normPrivateKeyToScalar, + weierstrassEquation, + isWithinCurveOrder + }; + } + function validateOpts2(curve) { + const opts = validateBasic2(curve); + validateObject2(opts, { + hash: "hash", + hmac: "function", + randomBytes: "function" + }, { + bits2int: "function", + bits2int_modN: "function", + lowS: "boolean" + }); + return Object.freeze({ lowS: true, ...opts }); + } + function weierstrass2(curveDef) { + const CURVE = validateOpts2(curveDef); + const { Fp: Fp3, n: CURVE_ORDER } = CURVE; + const compressedLen = Fp3.BYTES + 1; + const uncompressedLen = 2 * Fp3.BYTES + 1; + function isValidFieldElement(num) { + return _0n9 < num && num < Fp3.ORDER; + } + function modN2(a) { + return mod2(a, CURVE_ORDER); + } + function invN(a) { + return invert2(a, CURVE_ORDER); + } + const { ProjectivePoint: Point4, normPrivateKeyToScalar, weierstrassEquation, isWithinCurveOrder } = weierstrassPoints2({ + ...CURVE, + toBytes(c, point, isCompressed) { + const a = point.toAffine(); + const x = Fp3.toBytes(a.x); + const cat = concatBytes4; + if (isCompressed) { + return cat(Uint8Array.from([point.hasEvenY() ? 2 : 3]), x); + } else { + return cat(Uint8Array.from([4]), x, Fp3.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 = bytesToNumberBE2(tail); + if (!isValidFieldElement(x)) + throw new Error("Point is not on curve"); + const y2 = weierstrassEquation(x); + let y = Fp3.sqrt(y2); + const isYOdd = (y & _1n9) === _1n9; + const isHeadOdd = (head & 1) === 1; + if (isHeadOdd !== isYOdd) + y = Fp3.neg(y); + return { x, y }; + } else if (len === uncompressedLen && head === 4) { + const x = Fp3.fromBytes(tail.subarray(0, Fp3.BYTES)); + const y = Fp3.fromBytes(tail.subarray(Fp3.BYTES, 2 * Fp3.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) => bytesToHex3(numberToBytesBE2(num, CURVE.nByteLength)); + function isBiggerThanHalfOrder(number4) { + const HALF = CURVE_ORDER >> _1n9; + return number4 > HALF; + } + function normalizeS(s) { + return isBiggerThanHalfOrder(s) ? modN2(-s) : s; + } + const slcNum = (b, from, to) => bytesToNumberBE2(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 = ensureBytes2("compactSignature", hex2, l * 2); + return new Signature(slcNum(hex2, 0, l), slcNum(hex2, l, 2 * l)); + } + static fromDER(hex2) { + const { r, s } = DER2.toSig(ensureBytes2("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(ensureBytes2("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 >= Fp3.ORDER) + throw new Error("recovery id 2 or 3 invalid"); + const prefix = (rec & 1) === 0 ? "02" : "03"; + const R = Point4.fromHex(prefix + numToNByteStr(radj)); + const ir = invN(radj); + const u1 = modN2(-h * ir); + const u2 = modN2(s * ir); + const Q = Point4.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 hexToBytes3(this.toDERHex()); + } + toDERHex() { + return DER2.hexFromSig({ r: this.r, s: this.s }); + } + toCompactRawBytes() { + return hexToBytes3(this.toCompactHex()); + } + toCompactHex() { + return numToNByteStr(this.r) + numToNByteStr(this.s); + } + } + const utils2 = { + isValidPrivateKey(privateKey) { + try { + normPrivateKeyToScalar(privateKey); + return true; + } catch (error) { + return false; + } + }, + normPrivateKeyToScalar, + randomPrivateKey: () => { + const rand = CURVE.randomBytes(Fp3.BYTES + 8); + const num = hashToPrivateScalar(rand, CURVE_ORDER); + return numberToBytesBE2(num, CURVE.nByteLength); + }, + precompute(windowSize = 8, point = Point4.BASE) { + point._setWindowSize(windowSize); + point.multiply(BigInt(3)); + return point; + } + }; + function getPublicKey2(privateKey, isCompressed = true) { + return Point4.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 Point4) + 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 = Point4.fromHex(publicB); + return b.multiply(normPrivateKeyToScalar(privateA)).toRawBytes(isCompressed); + } + const bits2int = CURVE.bits2int || function(bytes4) { + const num = bytesToNumberBE2(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 = bitMask2(CURVE.nBitLength); + function int2octets(num) { + if (typeof num !== "bigint") + throw new Error("bigint expected"); + if (!(_0n9 <= num && num < ORDER_MASK)) + throw new Error(`bigint expected < 2^${CURVE.nBitLength}`); + return numberToBytesBE2(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 = ensureBytes2("msgHash", msgHash); + if (prehash) + msgHash = ensureBytes2("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(Fp3.BYTES) : ent; + seedArgs.push(ensureBytes2("extraEntropy", e, Fp3.BYTES)); + } + const seed = concatBytes4(...seedArgs); + const m = h1int; + function k2sig(kBytes) { + const k = bits2int(kBytes); + if (!isWithinCurveOrder(k)) + return; + const ik = invN(k); + const q = Point4.BASE.multiply(k).toAffine(); + const r = modN2(q.x); + if (r === _0n9) + return; + const s = modN2(ik * modN2(m + r * d)); + if (s === _0n9) + return; + let recovery = (q.x === r ? 0 : 2) | Number(q.y & _1n9); + 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 = createHmacDrbg2(C.hash.outputLen, C.nByteLength, C.hmac); + return drbg(seed, k2sig); + } + Point4.BASE._setWindowSize(8); + function verify(signature, msgHash, publicKey, opts = defaultVerOpts) { + const sg = signature; + msgHash = ensureBytes2("msgHash", msgHash); + publicKey = ensureBytes2("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 DER2.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 = Point4.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 = Point4.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: Point4, + Signature, + utils: utils2 + }; + } + + // node_modules/@scure/bip32/node_modules/@noble/curves/esm/_shortw_utils.js + function getHash2(hash3) { + return { + hash: hash3, + hmac: (key, ...msgs) => hmac2(hash3, key, concatBytes3(...msgs)), + randomBytes: randomBytes2 + }; + } + function createCurve2(curveDef, defHash) { + const create = (hash3) => weierstrass2({ ...curveDef, ...getHash2(hash3) }); + return Object.freeze({ ...create(defHash), create }); + } + + // node_modules/@scure/bip32/node_modules/@noble/curves/esm/secp256k1.js + var secp256k1P2 = BigInt("0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f"); + var secp256k1N2 = BigInt("0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141"); + var _1n10 = BigInt(1); + var _2n8 = BigInt(2); + var divNearest2 = (a, b) => (a + b / _2n8) / b; + function sqrtMod2(y) { + const P = secp256k1P2; + const _3n5 = 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 = pow22(b3, _3n5, P) * b3 % P; + const b9 = pow22(b6, _3n5, P) * b3 % P; + const b11 = pow22(b9, _2n8, P) * b2 % P; + const b22 = pow22(b11, _11n, P) * b11 % P; + const b44 = pow22(b22, _22n, P) * b22 % P; + const b88 = pow22(b44, _44n, P) * b44 % P; + const b176 = pow22(b88, _88n, P) * b88 % P; + const b220 = pow22(b176, _44n, P) * b44 % P; + const b223 = pow22(b220, _3n5, P) * b3 % P; + const t1 = pow22(b223, _23n, P) * b22 % P; + const t2 = pow22(t1, _6n, P) * b2 % P; + const root = pow22(t2, _2n8, P); + if (!Fp2.eql(Fp2.sqr(root), y)) + throw new Error("Cannot find square root"); + return root; + } + var Fp2 = Field2(secp256k1P2, void 0, void 0, { sqrt: sqrtMod2 }); + var secp256k12 = createCurve2({ + a: BigInt(0), + b: BigInt(7), + Fp: Fp2, + n: secp256k1N2, + Gx: BigInt("55066263022277343669578718895168534326250603453777594175500187360389116729240"), + Gy: BigInt("32670510020758816978083085130507043184471273380659243275938904335757337482424"), + h: BigInt(1), + lowS: true, + endo: { + beta: BigInt("0x7ae96a2b657c07106e64479eac3434e99cf0497512f58995c1396c28719501ee"), + splitScalar: (k) => { + const n = secp256k1N2; + const a1 = BigInt("0x3086d221a7d46bcde86c90e49284eb15"); + const b1 = -_1n10 * BigInt("0xe4437ed6010e88286f547fa90abfe4c3"); + const a2 = BigInt("0x114ca50f7a8e2f3f657c1108d9d44cfd8"); + const b2 = a1; + const POW_2_128 = BigInt("0x100000000000000000000000000000000"); + const c1 = divNearest2(b2 * k, n); + const c2 = divNearest2(-b1 * k, n); + let k1 = mod2(k - c1 * a1 - c2 * a2, n); + let k2 = mod2(-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 }; + } + } + }, sha2562); + var _0n10 = BigInt(0); + var Point2 = secp256k12.ProjectivePoint; + + // node_modules/@scure/bip32/lib/esm/index.js + var Point3 = secp256k12.ProjectivePoint; + var base58check2 = base58check(sha2562); + function bytesToNumber(bytes4) { + return BigInt(`0x${bytesToHex2(bytes4)}`); + } + function numberToBytes(num) { + return hexToBytes2(num.toString(16).padStart(64, "0")); + } + var MASTER_SECRET = utf8ToBytes3("Bitcoin seed"); + var BITCOIN_VERSIONS = { private: 76066276, public: 76067358 }; + var HARDENED_OFFSET = 2147483648; + var hash160 = (data) => ripemd160(sha2562(data)); + var fromU32 = (data) => createView2(data).getUint32(0, false); + var toU32 = (n) => { + if (!Number.isSafeInteger(n) || n < 0 || n > 2 ** 32 - 1) { + throw new Error(`Invalid number=${n}. Should be from 0 to 2 ** 32 - 1`); + } + const buf = new Uint8Array(4); + createView2(buf).setUint32(0, n, false); + return buf; + }; + var HDKey = class { + get fingerprint() { + if (!this.pubHash) { + throw new Error("No publicKey set!"); + } + return fromU32(this.pubHash); + } + get identifier() { + return this.pubHash; + } + get pubKeyHash() { + return this.pubHash; + } + get privateKey() { + return this.privKeyBytes || null; + } + get publicKey() { + return this.pubKey || null; + } + get privateExtendedKey() { + const priv = this.privateKey; + if (!priv) { + throw new Error("No private key"); + } + return base58check2.encode(this.serialize(this.versions.private, concatBytes3(new Uint8Array([0]), priv))); + } + get publicExtendedKey() { + if (!this.pubKey) { + throw new Error("No public key"); + } + return base58check2.encode(this.serialize(this.versions.public, this.pubKey)); + } + static fromMasterSeed(seed, versions = BITCOIN_VERSIONS) { + bytes2(seed); + if (8 * seed.length < 128 || 8 * seed.length > 512) { + throw new Error(`HDKey: wrong seed length=${seed.length}. Should be between 128 and 512 bits; 256 bits is advised)`); + } + const I = hmac2(sha512, MASTER_SECRET, seed); + return new HDKey({ + versions, + chainCode: I.slice(32), + privateKey: I.slice(0, 32) + }); + } + static fromExtendedKey(base58key, versions = BITCOIN_VERSIONS) { + const keyBuffer = base58check2.decode(base58key); + const keyView = createView2(keyBuffer); + const version = keyView.getUint32(0, false); + const opt = { + versions, + depth: keyBuffer[4], + parentFingerprint: keyView.getUint32(5, false), + index: keyView.getUint32(9, false), + chainCode: keyBuffer.slice(13, 45) + }; + const key = keyBuffer.slice(45); + const isPriv = key[0] === 0; + if (version !== versions[isPriv ? "private" : "public"]) { + throw new Error("Version mismatch"); + } + if (isPriv) { + return new HDKey({ ...opt, privateKey: key.slice(1) }); + } else { + return new HDKey({ ...opt, publicKey: key }); + } + } + static fromJSON(json) { + return HDKey.fromExtendedKey(json.xpriv); + } + constructor(opt) { + this.depth = 0; + this.index = 0; + this.chainCode = null; + this.parentFingerprint = 0; + if (!opt || typeof opt !== "object") { + throw new Error("HDKey.constructor must not be called directly"); + } + this.versions = opt.versions || BITCOIN_VERSIONS; + this.depth = opt.depth || 0; + this.chainCode = opt.chainCode; + this.index = opt.index || 0; + this.parentFingerprint = opt.parentFingerprint || 0; + if (!this.depth) { + if (this.parentFingerprint || this.index) { + throw new Error("HDKey: zero depth with non-zero index/parent fingerprint"); + } + } + if (opt.publicKey && opt.privateKey) { + throw new Error("HDKey: publicKey and privateKey at same time."); + } + if (opt.privateKey) { + if (!secp256k12.utils.isValidPrivateKey(opt.privateKey)) { + throw new Error("Invalid private key"); + } + this.privKey = typeof opt.privateKey === "bigint" ? opt.privateKey : bytesToNumber(opt.privateKey); + this.privKeyBytes = numberToBytes(this.privKey); + this.pubKey = secp256k12.getPublicKey(opt.privateKey, true); + } else if (opt.publicKey) { + this.pubKey = Point3.fromHex(opt.publicKey).toRawBytes(true); + } else { + throw new Error("HDKey: no public or private key provided"); + } + this.pubHash = hash160(this.pubKey); + } + derive(path) { + if (!/^[mM]'?/.test(path)) { + throw new Error('Path must start with "m" or "M"'); + } + if (/^[mM]'?$/.test(path)) { + return this; + } + const parts = path.replace(/^[mM]'?\//, "").split("/"); + let child = this; + for (const c of parts) { + const m = /^(\d+)('?)$/.exec(c); + if (!m || m.length !== 3) { + throw new Error(`Invalid child index: ${c}`); + } + let idx = +m[1]; + if (!Number.isSafeInteger(idx) || idx >= HARDENED_OFFSET) { + throw new Error("Invalid index"); + } + if (m[2] === "'") { + idx += HARDENED_OFFSET; + } + child = child.deriveChild(idx); + } + return child; + } + deriveChild(index) { + if (!this.pubKey || !this.chainCode) { + throw new Error("No publicKey or chainCode set"); + } + let data = toU32(index); + if (index >= HARDENED_OFFSET) { + const priv = this.privateKey; + if (!priv) { + throw new Error("Could not derive hardened child key"); + } + data = concatBytes3(new Uint8Array([0]), priv, data); + } else { + data = concatBytes3(this.pubKey, data); + } + const I = hmac2(sha512, this.chainCode, data); + const childTweak = bytesToNumber(I.slice(0, 32)); + const chainCode = I.slice(32); + if (!secp256k12.utils.isValidPrivateKey(childTweak)) { + throw new Error("Tweak bigger than curve order"); + } + const opt = { + versions: this.versions, + chainCode, + depth: this.depth + 1, + parentFingerprint: this.fingerprint, + index + }; + try { + if (this.privateKey) { + const added = mod2(this.privKey + childTweak, secp256k12.CURVE.n); + if (!secp256k12.utils.isValidPrivateKey(added)) { + throw new Error("The tweak was out of range or the resulted private key is invalid"); + } + opt.privateKey = added; + } else { + const added = Point3.fromHex(this.pubKey).add(Point3.fromPrivateKey(childTweak)); + if (added.equals(Point3.ZERO)) { + throw new Error("The tweak was equal to negative P, which made the result key invalid"); + } + opt.publicKey = added.toRawBytes(true); + } + return new HDKey(opt); + } catch (err) { + return this.deriveChild(index + 1); + } + } + sign(hash3) { + if (!this.privateKey) { + throw new Error("No privateKey set!"); + } + bytes2(hash3, 32); + return secp256k12.sign(hash3, this.privKey).toCompactRawBytes(); + } + verify(hash3, signature) { + bytes2(hash3, 32); + bytes2(signature, 64); + if (!this.publicKey) { + throw new Error("No publicKey set!"); + } + let sig; + try { + sig = secp256k12.Signature.fromCompact(signature); + } catch (error) { + return false; + } + return secp256k12.verify(sig, hash3, this.publicKey); + } + wipePrivateData() { + this.privKey = void 0; + if (this.privKeyBytes) { + this.privKeyBytes.fill(0); + this.privKeyBytes = void 0; + } + return this; + } + toJSON() { + return { + xpriv: this.privateExtendedKey, + xpub: this.publicExtendedKey + }; + } + serialize(version, key) { + if (!this.chainCode) { + throw new Error("No chainCode set"); + } + bytes2(key, 33); + return concatBytes3(toU32(version), new Uint8Array([this.depth]), toU32(this.parentFingerprint), toU32(this.index), this.chainCode, key); + } + }; + + // nip06.ts + var DERIVATION_PATH = `m/44'/1237'`; + function privateKeyFromSeedWords(mnemonic, passphrase, accountIndex = 0) { + let root = HDKey.fromMasterSeed(mnemonicToSeedSync(mnemonic, passphrase)); + let privateKey = root.derive(`${DERIVATION_PATH}/${accountIndex}'/0/0`).privateKey; + if (!privateKey) + throw new Error("could not derive private key"); + return privateKey; + } + function accountFromSeedWords(mnemonic, passphrase, accountIndex = 0) { + const root = HDKey.fromMasterSeed(mnemonicToSeedSync(mnemonic, passphrase)); + const seed = root.derive(`${DERIVATION_PATH}/${accountIndex}'/0/0`); + const publicKey = bytesToHex2(seed.publicKey.slice(1)); + const privateKey = seed.privateKey; + if (!privateKey || !publicKey) { + throw new Error("could not derive key pair"); + } + return { privateKey, publicKey }; + } + function extendedKeysFromSeedWords(mnemonic, passphrase, extendedAccountIndex = 0) { + let root = HDKey.fromMasterSeed(mnemonicToSeedSync(mnemonic, passphrase)); + let seed = root.derive(`${DERIVATION_PATH}/${extendedAccountIndex}'`); + let privateExtendedKey = seed.privateExtendedKey; + let publicExtendedKey = seed.publicExtendedKey; + if (!privateExtendedKey && !publicExtendedKey) + throw new Error("could not derive extended key pair"); + return { privateExtendedKey, publicExtendedKey }; + } + function accountFromExtendedKey(base58key, accountIndex = 0) { + let extendedKey = HDKey.fromExtendedKey(base58key); + let version = base58key.slice(0, 4); + let child = extendedKey.deriveChild(0).deriveChild(accountIndex); + let publicKey = bytesToHex2(child.publicKey.slice(1)); + if (!publicKey) + throw new Error("could not derive public key"); + if (version === "xprv") { + let privateKey = child.privateKey; + if (!privateKey) + throw new Error("could not derive private key"); + return { privateKey, publicKey }; + } + return { publicKey }; + } + function generateSeedWords() { + return generateMnemonic(wordlist); + } + function validateWords(words) { + return validateMnemonic(words, wordlist); + } + // nip10.ts var nip10_exports = {}; __export(nip10_exports, { @@ -5406,11 +9783,11 @@ var NostrTools = (() => { 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; + let f2 = h[0] + pad2[0]; + h[0] = f2 & 65535; for (let i2 = 1; i2 < 8; i2++) { - f = (h[i2] + pad2[i2] | 0) + (f >>> 16) | 0; - h[i2] = f & 65535; + f2 = (h[i2] + pad2[i2] | 0) + (f2 >>> 16) | 0; + h[i2] = f2 & 65535; } } update(data) { @@ -5480,11 +9857,13 @@ var NostrTools = (() => { 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 _utf8ToBytes = (str) => Uint8Array.from(str.split("").map((c) => c.charCodeAt(0))); + var sigma16 = _utf8ToBytes("expand 16-byte k"); + var sigma32 = _utf8ToBytes("expand 32-byte k"); var sigma16_32 = u32(sigma16); var sigma32_32 = u32(sigma32); - function rotl(a, b) { + var sigma = sigma32_32.slice(); + function rotl2(a, b) { return a << b | a >>> 32 - b; } function isAligned32(b) { @@ -5494,7 +9873,7 @@ var NostrTools = (() => { 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) { + function runCipher(core, sigma2, key, nonce, data, output4, counter, rounds) { const len = data.length; const block = new Uint8Array(BLOCK_LEN); const b32 = u32(block); @@ -5502,7 +9881,7 @@ var NostrTools = (() => { 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); + core(sigma2, key, nonce, b32, counter, rounds); if (counter >= MAX_COUNTER) throw new Error("arx: counter overflow"); const take = Math.min(BLOCK_LEN, len - pos); @@ -5525,7 +9904,7 @@ var NostrTools = (() => { } } function createCipher(core, opts) { - const { allowShortKeys, extendNonceFn, counterLength, counterRight, rounds } = checkOpts({ allowShortKeys: false, counterLength: 8, counterRight: false, rounds: 20 }, opts); + const { allowShortKeys, extendNonceFn, counterLength, counterRight, rounds } = checkOpts2({ allowShortKeys: false, counterLength: 8, counterRight: false, rounds: 20 }, opts); if (typeof core !== "function") throw new Error("core must be a function"); number3(counterLength); @@ -5546,16 +9925,16 @@ var NostrTools = (() => { if (output4.length < len) throw new Error(`arx: output (${output4.length}) is shorter than data (${len})`); const toClean = []; - let l = key.length, k, sigma; + let l = key.length, k, sigma2; if (l === 32) { k = key.slice(); toClean.push(k); - sigma = sigma32_32; + sigma2 = sigma32_32; } else if (l === 16 && allowShortKeys) { k = new Uint8Array(32); k.set(key); k.set(key, 16); - sigma = sigma16_32; + sigma2 = sigma16_32; toClean.push(k); } else { throw new Error(`arx: invalid 32-byte key, got length=${l}`); @@ -5568,7 +9947,7 @@ var NostrTools = (() => { 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); + extendNonceFn(sigma2, k32, u32(nonce.subarray(0, 16)), k32); nonce = nonce.subarray(16); } const nonceNcLen = 16 - counterLength; @@ -5581,7 +9960,7 @@ var NostrTools = (() => { toClean.push(nonce); } const n32 = u32(nonce); - runCipher(core, sigma, k32, n32, data, output4, counter, rounds); + runCipher(core, sigma2, k32, n32, data, output4, counter, rounds); while (toClean.length > 0) toClean.pop().fill(0); return output4; @@ -5594,69 +9973,69 @@ var NostrTools = (() => { 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); + x12 = rotl2(x12 ^ x00, 16); x08 = x08 + x12 | 0; - x04 = rotl(x04 ^ x08, 12); + x04 = rotl2(x04 ^ x08, 12); x00 = x00 + x04 | 0; - x12 = rotl(x12 ^ x00, 8); + x12 = rotl2(x12 ^ x00, 8); x08 = x08 + x12 | 0; - x04 = rotl(x04 ^ x08, 7); + x04 = rotl2(x04 ^ x08, 7); x01 = x01 + x05 | 0; - x13 = rotl(x13 ^ x01, 16); + x13 = rotl2(x13 ^ x01, 16); x09 = x09 + x13 | 0; - x05 = rotl(x05 ^ x09, 12); + x05 = rotl2(x05 ^ x09, 12); x01 = x01 + x05 | 0; - x13 = rotl(x13 ^ x01, 8); + x13 = rotl2(x13 ^ x01, 8); x09 = x09 + x13 | 0; - x05 = rotl(x05 ^ x09, 7); + x05 = rotl2(x05 ^ x09, 7); x02 = x02 + x06 | 0; - x14 = rotl(x14 ^ x02, 16); + x14 = rotl2(x14 ^ x02, 16); x10 = x10 + x14 | 0; - x06 = rotl(x06 ^ x10, 12); + x06 = rotl2(x06 ^ x10, 12); x02 = x02 + x06 | 0; - x14 = rotl(x14 ^ x02, 8); + x14 = rotl2(x14 ^ x02, 8); x10 = x10 + x14 | 0; - x06 = rotl(x06 ^ x10, 7); + x06 = rotl2(x06 ^ x10, 7); x03 = x03 + x07 | 0; - x15 = rotl(x15 ^ x03, 16); + x15 = rotl2(x15 ^ x03, 16); x11 = x11 + x15 | 0; - x07 = rotl(x07 ^ x11, 12); + x07 = rotl2(x07 ^ x11, 12); x03 = x03 + x07 | 0; - x15 = rotl(x15 ^ x03, 8); + x15 = rotl2(x15 ^ x03, 8); x11 = x11 + x15 | 0; - x07 = rotl(x07 ^ x11, 7); + x07 = rotl2(x07 ^ x11, 7); x00 = x00 + x05 | 0; - x15 = rotl(x15 ^ x00, 16); + x15 = rotl2(x15 ^ x00, 16); x10 = x10 + x15 | 0; - x05 = rotl(x05 ^ x10, 12); + x05 = rotl2(x05 ^ x10, 12); x00 = x00 + x05 | 0; - x15 = rotl(x15 ^ x00, 8); + x15 = rotl2(x15 ^ x00, 8); x10 = x10 + x15 | 0; - x05 = rotl(x05 ^ x10, 7); + x05 = rotl2(x05 ^ x10, 7); x01 = x01 + x06 | 0; - x12 = rotl(x12 ^ x01, 16); + x12 = rotl2(x12 ^ x01, 16); x11 = x11 + x12 | 0; - x06 = rotl(x06 ^ x11, 12); + x06 = rotl2(x06 ^ x11, 12); x01 = x01 + x06 | 0; - x12 = rotl(x12 ^ x01, 8); + x12 = rotl2(x12 ^ x01, 8); x11 = x11 + x12 | 0; - x06 = rotl(x06 ^ x11, 7); + x06 = rotl2(x06 ^ x11, 7); x02 = x02 + x07 | 0; - x13 = rotl(x13 ^ x02, 16); + x13 = rotl2(x13 ^ x02, 16); x08 = x08 + x13 | 0; - x07 = rotl(x07 ^ x08, 12); + x07 = rotl2(x07 ^ x08, 12); x02 = x02 + x07 | 0; - x13 = rotl(x13 ^ x02, 8); + x13 = rotl2(x13 ^ x02, 8); x08 = x08 + x13 | 0; - x07 = rotl(x07 ^ x08, 7); + x07 = rotl2(x07 ^ x08, 7); x03 = x03 + x04 | 0; - x14 = rotl(x14 ^ x03, 16); + x14 = rotl2(x14 ^ x03, 16); x09 = x09 + x14 | 0; - x04 = rotl(x04 ^ x09, 12); + x04 = rotl2(x04 ^ x09, 12); x03 = x03 + x04 | 0; - x14 = rotl(x14 ^ x03, 8); + x14 = rotl2(x14 ^ x03, 8); x09 = x09 + x14 | 0; - x04 = rotl(x04 ^ x09, 7); + x04 = rotl2(x04 ^ x09, 7); } let oi = 0; out[oi++] = y00 + x00 | 0; @@ -5680,69 +10059,69 @@ var NostrTools = (() => { 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); + x12 = rotl2(x12 ^ x00, 16); x08 = x08 + x12 | 0; - x04 = rotl(x04 ^ x08, 12); + x04 = rotl2(x04 ^ x08, 12); x00 = x00 + x04 | 0; - x12 = rotl(x12 ^ x00, 8); + x12 = rotl2(x12 ^ x00, 8); x08 = x08 + x12 | 0; - x04 = rotl(x04 ^ x08, 7); + x04 = rotl2(x04 ^ x08, 7); x01 = x01 + x05 | 0; - x13 = rotl(x13 ^ x01, 16); + x13 = rotl2(x13 ^ x01, 16); x09 = x09 + x13 | 0; - x05 = rotl(x05 ^ x09, 12); + x05 = rotl2(x05 ^ x09, 12); x01 = x01 + x05 | 0; - x13 = rotl(x13 ^ x01, 8); + x13 = rotl2(x13 ^ x01, 8); x09 = x09 + x13 | 0; - x05 = rotl(x05 ^ x09, 7); + x05 = rotl2(x05 ^ x09, 7); x02 = x02 + x06 | 0; - x14 = rotl(x14 ^ x02, 16); + x14 = rotl2(x14 ^ x02, 16); x10 = x10 + x14 | 0; - x06 = rotl(x06 ^ x10, 12); + x06 = rotl2(x06 ^ x10, 12); x02 = x02 + x06 | 0; - x14 = rotl(x14 ^ x02, 8); + x14 = rotl2(x14 ^ x02, 8); x10 = x10 + x14 | 0; - x06 = rotl(x06 ^ x10, 7); + x06 = rotl2(x06 ^ x10, 7); x03 = x03 + x07 | 0; - x15 = rotl(x15 ^ x03, 16); + x15 = rotl2(x15 ^ x03, 16); x11 = x11 + x15 | 0; - x07 = rotl(x07 ^ x11, 12); + x07 = rotl2(x07 ^ x11, 12); x03 = x03 + x07 | 0; - x15 = rotl(x15 ^ x03, 8); + x15 = rotl2(x15 ^ x03, 8); x11 = x11 + x15 | 0; - x07 = rotl(x07 ^ x11, 7); + x07 = rotl2(x07 ^ x11, 7); x00 = x00 + x05 | 0; - x15 = rotl(x15 ^ x00, 16); + x15 = rotl2(x15 ^ x00, 16); x10 = x10 + x15 | 0; - x05 = rotl(x05 ^ x10, 12); + x05 = rotl2(x05 ^ x10, 12); x00 = x00 + x05 | 0; - x15 = rotl(x15 ^ x00, 8); + x15 = rotl2(x15 ^ x00, 8); x10 = x10 + x15 | 0; - x05 = rotl(x05 ^ x10, 7); + x05 = rotl2(x05 ^ x10, 7); x01 = x01 + x06 | 0; - x12 = rotl(x12 ^ x01, 16); + x12 = rotl2(x12 ^ x01, 16); x11 = x11 + x12 | 0; - x06 = rotl(x06 ^ x11, 12); + x06 = rotl2(x06 ^ x11, 12); x01 = x01 + x06 | 0; - x12 = rotl(x12 ^ x01, 8); + x12 = rotl2(x12 ^ x01, 8); x11 = x11 + x12 | 0; - x06 = rotl(x06 ^ x11, 7); + x06 = rotl2(x06 ^ x11, 7); x02 = x02 + x07 | 0; - x13 = rotl(x13 ^ x02, 16); + x13 = rotl2(x13 ^ x02, 16); x08 = x08 + x13 | 0; - x07 = rotl(x07 ^ x08, 12); + x07 = rotl2(x07 ^ x08, 12); x02 = x02 + x07 | 0; - x13 = rotl(x13 ^ x02, 8); + x13 = rotl2(x13 ^ x02, 8); x08 = x08 + x13 | 0; - x07 = rotl(x07 ^ x08, 7); + x07 = rotl2(x07 ^ x08, 7); x03 = x03 + x04 | 0; - x14 = rotl(x14 ^ x03, 16); + x14 = rotl2(x14 ^ x03, 16); x09 = x09 + x14 | 0; - x04 = rotl(x04 ^ x09, 12); + x04 = rotl2(x04 ^ x09, 12); x03 = x03 + x04 | 0; - x14 = rotl(x14 ^ x03, 8); + x14 = rotl2(x14 ^ x03, 8); x09 = x09 + x14 | 0; - x04 = rotl(x04 ^ x09, 7); + x04 = rotl2(x04 ^ x09, 7); } let oi = 0; o32[oi++] = x00; @@ -5829,77 +10208,12 @@ var NostrTools = (() => { 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) { + function extract(hash3, ikm, salt2) { assert_default.hash(hash3); - if (salt === void 0) - salt = new Uint8Array(hash3.outputLen); - return hmac2(hash3, toBytes2(salt), toBytes2(ikm)); + if (salt2 === void 0) + salt2 = new Uint8Array(hash3.outputLen); + return hmac2(hash3, toBytes2(salt2), toBytes2(ikm)); } var HKDF_COUNTER = new Uint8Array([0]); var EMPTY_BUFFER = new Uint8Array(); @@ -6329,19 +10643,19 @@ var NostrTools = (() => { 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")) { + if (/\.(png|jpe?g|gif|webp)$/i.test(url.pathname)) { 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")) { + if (/\.(mp4|avi|webm|mkv)$/i.test(url.pathname)) { 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")) { + if (/\.(mp3|aac|ogg|opus)$/i.test(url.pathname)) { yield { type: "audio", url: url.toString() }; index = end; prevIndex = index; @@ -6543,6 +10857,366 @@ var NostrTools = (() => { } } + // nip46.ts + var nip46_exports = {}; + __export(nip46_exports, { + BUNKER_REGEX: () => BUNKER_REGEX, + BunkerSigner: () => BunkerSigner, + createAccount: () => createAccount, + createNostrConnectURI: () => createNostrConnectURI, + fetchBunkerProviders: () => fetchBunkerProviders, + parseBunkerInput: () => parseBunkerInput, + parseNostrConnectURI: () => parseNostrConnectURI, + queryBunkerProfile: () => queryBunkerProfile, + toBunkerURL: () => toBunkerURL, + useFetchImplementation: () => useFetchImplementation4 + }); + var _fetch4; + try { + _fetch4 = fetch; + } catch { + } + function useFetchImplementation4(fetchImplementation) { + _fetch4 = fetchImplementation; + } + var BUNKER_REGEX = /^bunker:\/\/([0-9a-f]{64})\??([?\/\w:.=&%-]*)$/; + var EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + function toBunkerURL(bunkerPointer) { + let bunkerURL = new URL(`bunker://${bunkerPointer.pubkey}`); + bunkerPointer.relays.forEach((relay) => { + bunkerURL.searchParams.append("relay", relay); + }); + if (bunkerPointer.secret) { + bunkerURL.searchParams.set("secret", bunkerPointer.secret); + } + return bunkerURL.toString(); + } + async function parseBunkerInput(input) { + let match = input.match(BUNKER_REGEX); + if (match) { + try { + const pubkey = match[1]; + const qs = new URLSearchParams(match[2]); + return { + pubkey, + relays: qs.getAll("relay"), + secret: qs.get("secret") + }; + } catch (_err) { + } + } + return queryBunkerProfile(input); + } + async function queryBunkerProfile(nip05) { + const match = nip05.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 (await _fetch4(url, { redirect: "error" })).json(); + let pubkey = res.names[name]; + let relays = res.nip46[pubkey] || []; + return { pubkey, relays, secret: null }; + } catch (_err) { + return null; + } + } + function createNostrConnectURI(params) { + if (!params.clientPubkey) { + throw new Error("clientPubkey is required."); + } + if (!params.relays || params.relays.length === 0) { + throw new Error("At least one relay is required."); + } + if (!params.secret) { + throw new Error("secret is required."); + } + const queryParams = new URLSearchParams(); + params.relays.forEach((relay) => { + queryParams.append("relay", relay); + }); + queryParams.append("secret", params.secret); + if (params.perms && params.perms.length > 0) { + queryParams.append("perms", params.perms.join(",")); + } + if (params.name) { + queryParams.append("name", params.name); + } + if (params.url) { + queryParams.append("url", params.url); + } + if (params.image) { + queryParams.append("image", params.image); + } + return `nostrconnect://${params.clientPubkey}?${queryParams.toString()}`; + } + function parseNostrConnectURI(uri) { + if (!uri.startsWith("nostrconnect://")) { + throw new Error('Invalid nostrconnect URI: Must start with "nostrconnect://".'); + } + const [protocolAndPubkey, queryString] = uri.split("?"); + if (!protocolAndPubkey || !queryString) { + throw new Error("Invalid nostrconnect URI: Missing query string."); + } + const clientPubkey = protocolAndPubkey.substring("nostrconnect://".length); + if (!clientPubkey) { + throw new Error("Invalid nostrconnect URI: Missing client-pubkey."); + } + const queryParams = new URLSearchParams(queryString); + const relays = queryParams.getAll("relay"); + if (relays.length === 0) { + throw new Error('Invalid nostrconnect URI: Missing "relay" parameter.'); + } + const secret = queryParams.get("secret"); + if (!secret) { + throw new Error('Invalid nostrconnect URI: Missing "secret" parameter.'); + } + const permsString = queryParams.get("perms"); + const perms = permsString ? permsString.split(",") : void 0; + const name = queryParams.get("name") || void 0; + const url = queryParams.get("url") || void 0; + const image = queryParams.get("image") || void 0; + return { + protocol: "nostrconnect", + clientPubkey, + params: { + relays, + secret, + perms, + name, + url, + image + }, + originalString: uri + }; + } + var BunkerSigner = class { + params; + pool; + subCloser; + isOpen; + serial; + idPrefix; + listeners; + waitingForAuth; + secretKey; + conversationKey; + bp; + cachedPubKey; + constructor(clientSecretKey, params) { + this.params = params; + this.pool = params.pool || new SimplePool(); + this.secretKey = clientSecretKey; + this.isOpen = false; + this.idPrefix = Math.random().toString(36).substring(7); + this.serial = 0; + this.listeners = {}; + this.waitingForAuth = {}; + } + static fromBunker(clientSecretKey, bp, params = {}) { + if (bp.relays.length === 0) { + throw new Error("No relays specified for this bunker"); + } + const signer = new BunkerSigner(clientSecretKey, params); + signer.conversationKey = getConversationKey(clientSecretKey, bp.pubkey); + signer.bp = bp; + signer.setupSubscription(params); + return signer; + } + static async fromURI(clientSecretKey, connectionURI, params = {}, maxWait = 3e5) { + const signer = new BunkerSigner(clientSecretKey, params); + const parsedURI = parseNostrConnectURI(connectionURI); + const clientPubkey = getPublicKey(clientSecretKey); + return new Promise((resolve, reject) => { + const timer = setTimeout(() => { + sub.close(); + reject(new Error(`Connection timed out after ${maxWait / 1e3} seconds`)); + }, maxWait); + const sub = signer.pool.subscribe( + parsedURI.params.relays, + { kinds: [NostrConnect], "#p": [clientPubkey] }, + { + onevent: async (event) => { + try { + const tempConvKey = getConversationKey(clientSecretKey, event.pubkey); + const decryptedContent = decrypt3(event.content, tempConvKey); + const response = JSON.parse(decryptedContent); + if (response.result === parsedURI.params.secret) { + clearTimeout(timer); + sub.close(); + signer.bp = { + pubkey: event.pubkey, + relays: parsedURI.params.relays, + secret: parsedURI.params.secret + }; + signer.conversationKey = getConversationKey(clientSecretKey, event.pubkey); + signer.setupSubscription(params); + resolve(signer); + } + } catch (e) { + console.warn("Failed to process potential connection event", e); + } + }, + onclose: () => { + clearTimeout(timer); + reject(new Error("Subscription closed before connection was established.")); + }, + maxWait + } + ); + }); + } + setupSubscription(params) { + const listeners = this.listeners; + const waitingForAuth = this.waitingForAuth; + const convKey = this.conversationKey; + this.subCloser = this.pool.subscribe( + this.bp.relays, + { kinds: [NostrConnect], authors: [this.bp.pubkey], "#p": [getPublicKey(this.secretKey)] }, + { + onevent: async (event) => { + const o = JSON.parse(decrypt3(event.content, convKey)); + const { id, result, error } = o; + if (result === "auth_url" && waitingForAuth[id]) { + delete waitingForAuth[id]; + if (params.onauth) { + params.onauth(error); + } else { + console.warn( + `nostr-tools/nip46: remote signer ${this.bp.pubkey} tried to send an "auth_url"='${error}' but there was no onauth() callback configured.` + ); + } + return; + } + let handler = listeners[id]; + if (handler) { + if (error) + handler.reject(error); + else if (result) + handler.resolve(result); + delete listeners[id]; + } + }, + onclose: () => { + this.subCloser = void 0; + } + } + ); + this.isOpen = true; + } + async close() { + this.isOpen = false; + this.subCloser.close(); + } + async sendRequest(method, params) { + return new Promise(async (resolve, reject) => { + try { + if (!this.isOpen) + throw new Error("this signer is not open anymore, create a new one"); + if (!this.subCloser) + this.setupSubscription(this.params); + this.serial++; + const id = `${this.idPrefix}-${this.serial}`; + const encryptedContent = encrypt3(JSON.stringify({ id, method, params }), this.conversationKey); + const verifiedEvent = finalizeEvent( + { + kind: NostrConnect, + tags: [["p", this.bp.pubkey]], + content: encryptedContent, + created_at: Math.floor(Date.now() / 1e3) + }, + this.secretKey + ); + this.listeners[id] = { resolve, reject }; + this.waitingForAuth[id] = true; + await Promise.any(this.pool.publish(this.bp.relays, verifiedEvent)); + } catch (err) { + reject(err); + } + }); + } + async ping() { + let resp = await this.sendRequest("ping", []); + if (resp !== "pong") + throw new Error(`result is not pong: ${resp}`); + } + async connect() { + await this.sendRequest("connect", [this.bp.pubkey, this.bp.secret || ""]); + } + async getPublicKey() { + if (!this.cachedPubKey) { + this.cachedPubKey = await this.sendRequest("get_public_key", []); + } + return this.cachedPubKey; + } + async signEvent(event) { + let resp = await this.sendRequest("sign_event", [JSON.stringify(event)]); + let signed = JSON.parse(resp); + if (verifyEvent(signed)) { + return signed; + } else { + throw new Error(`event returned from bunker is improperly signed: ${JSON.stringify(signed)}`); + } + } + async nip04Encrypt(thirdPartyPubkey, plaintext) { + return await this.sendRequest("nip04_encrypt", [thirdPartyPubkey, plaintext]); + } + async nip04Decrypt(thirdPartyPubkey, ciphertext) { + return await this.sendRequest("nip04_decrypt", [thirdPartyPubkey, ciphertext]); + } + async nip44Encrypt(thirdPartyPubkey, plaintext) { + return await this.sendRequest("nip44_encrypt", [thirdPartyPubkey, plaintext]); + } + async nip44Decrypt(thirdPartyPubkey, ciphertext) { + return await this.sendRequest("nip44_decrypt", [thirdPartyPubkey, ciphertext]); + } + }; + async function createAccount(bunker, params, username, domain, email, localSecretKey = generateSecretKey()) { + if (email && !EMAIL_REGEX.test(email)) + throw new Error("Invalid email"); + let rpc = BunkerSigner.fromBunker(localSecretKey, bunker.bunkerPointer, params); + let pubkey = await rpc.sendRequest("create_account", [username, domain, email || ""]); + rpc.bp.pubkey = pubkey; + await rpc.connect(); + return rpc; + } + async function fetchBunkerProviders(pool, relays) { + const events = await pool.querySync(relays, { + kinds: [Handlerinformation], + "#k": [NostrConnect.toString()] + }); + events.sort((a, b) => b.created_at - a.created_at); + const validatedBunkers = await Promise.all( + events.map(async (event, i2) => { + try { + const content = JSON.parse(event.content); + try { + if (events.findIndex((ev) => JSON.parse(ev.content).nip05 === content.nip05) !== i2) + return void 0; + } catch (err) { + } + const bp = await queryBunkerProfile(content.nip05); + if (bp && bp.pubkey === event.pubkey && bp.relays.length) { + return { + bunkerPointer: bp, + nip05: content.nip05, + domain: content.nip05.split("@")[1], + name: content.name || content.display_name, + picture: content.picture, + about: content.about, + website: content.website, + local: false + }; + } + } catch (err) { + return void 0; + } + }) + ); + return validatedBunkers.filter((b) => b !== void 0); + } + // nip47.ts var nip47_exports = {}; __export(nip47_exports, { @@ -6599,16 +11273,16 @@ var NostrTools = (() => { getZapEndpoint: () => getZapEndpoint, makeZapReceipt: () => makeZapReceipt, makeZapRequest: () => makeZapRequest, - useFetchImplementation: () => useFetchImplementation4, + useFetchImplementation: () => useFetchImplementation5, validateZapRequest: () => validateZapRequest }); - var _fetch4; + var _fetch5; try { - _fetch4 = fetch; + _fetch5 = fetch; } catch { } - function useFetchImplementation4(fetchImplementation) { - _fetch4 = fetchImplementation; + function useFetchImplementation5(fetchImplementation) { + _fetch5 = fetchImplementation; } async function getZapEndpoint(metadata) { try { @@ -6624,7 +11298,7 @@ var NostrTools = (() => { } else { return null; } - let res = await _fetch4(lnurl); + let res = await _fetch5(lnurl); let body = await res.json(); if (body.allowsNostr && body.nostrPubkey) { return body.callback; diff --git a/relay.pid b/relay.pid index a31b305..1ccf48a 100644 --- a/relay.pid +++ b/relay.pid @@ -1 +1 @@ -134045 +295261 diff --git a/src/config.c b/src/config.c index 460f10d..8d385d5 100644 --- a/src/config.c +++ b/src/config.c @@ -14,8 +14,12 @@ // External database connection (from main.c) extern sqlite3* g_db; -// Global configuration manager instance -config_manager_t g_config_manager = {0}; +// Global unified configuration cache instance +unified_config_cache_t g_unified_cache = { + .cache_lock = PTHREAD_MUTEX_INITIALIZER, + .cache_valid = 0, + .cache_expires = 0 +}; char g_database_path[512] = {0}; // ================================ @@ -78,6 +82,126 @@ static cJSON* g_pending_config_event = NULL; // Temporary storage for relay private key during first-time setup static char g_temp_relay_privkey[65] = {0}; +// ================================ +// UNIFIED CACHE MANAGEMENT FUNCTIONS +// ================================ + +// Get cache timeout from environment variable or default (similar to request_validator) +static int get_cache_timeout(void) { + char *no_cache = getenv("GINX_NO_CACHE"); + char *cache_timeout = getenv("GINX_CACHE_TIMEOUT"); + + if (no_cache && strcmp(no_cache, "1") == 0) { + return 0; // No caching + } + + if (cache_timeout) { + int timeout = atoi(cache_timeout); + return (timeout >= 0) ? timeout : 300; // Use provided value or default + } + + return 300; // Default 5 minutes +} + +// Force cache refresh - invalidates current cache +void force_config_cache_refresh(void) { + pthread_mutex_lock(&g_unified_cache.cache_lock); + g_unified_cache.cache_valid = 0; + g_unified_cache.cache_expires = 0; + pthread_mutex_unlock(&g_unified_cache.cache_lock); + log_info("Configuration cache forcibly invalidated"); +} + +// Refresh unified cache from database +static int refresh_unified_cache_from_table(void) { + if (!g_db) { + log_error("Database not available for cache refresh"); + return -1; + } + + // Clear cache + memset(&g_unified_cache, 0, sizeof(g_unified_cache)); + g_unified_cache.cache_lock = (pthread_mutex_t)PTHREAD_MUTEX_INITIALIZER; + + // Load critical config values from table + const char* admin_pubkey = get_config_value_from_table("admin_pubkey"); + if (admin_pubkey) { + strncpy(g_unified_cache.admin_pubkey, admin_pubkey, sizeof(g_unified_cache.admin_pubkey) - 1); + g_unified_cache.admin_pubkey[sizeof(g_unified_cache.admin_pubkey) - 1] = '\0'; + } + + const char* relay_pubkey = get_config_value_from_table("relay_pubkey"); + if (relay_pubkey) { + strncpy(g_unified_cache.relay_pubkey, relay_pubkey, sizeof(g_unified_cache.relay_pubkey) - 1); + g_unified_cache.relay_pubkey[sizeof(g_unified_cache.relay_pubkey) - 1] = '\0'; + } + + // Load auth-related config + const char* auth_required = get_config_value_from_table("auth_required"); + g_unified_cache.auth_required = (auth_required && strcmp(auth_required, "true") == 0) ? 1 : 0; + + const char* admin_enabled = get_config_value_from_table("admin_enabled"); + g_unified_cache.admin_enabled = (admin_enabled && strcmp(admin_enabled, "true") == 0) ? 1 : 0; + + const char* max_file_size = get_config_value_from_table("max_file_size"); + g_unified_cache.max_file_size = max_file_size ? atol(max_file_size) : 104857600; // 100MB default + + const char* nip42_mode = get_config_value_from_table("nip42_mode"); + if (nip42_mode) { + if (strcmp(nip42_mode, "disabled") == 0) { + g_unified_cache.nip42_mode = 0; + } else if (strcmp(nip42_mode, "required") == 0) { + g_unified_cache.nip42_mode = 2; + } else { + g_unified_cache.nip42_mode = 1; // Optional/enabled + } + } else { + g_unified_cache.nip42_mode = 1; // Default to optional/enabled + } + + const char* challenge_timeout = get_config_value_from_table("nip42_challenge_timeout"); + g_unified_cache.nip42_challenge_timeout = challenge_timeout ? atoi(challenge_timeout) : 600; + + const char* time_tolerance = get_config_value_from_table("nip42_time_tolerance"); + g_unified_cache.nip42_time_tolerance = time_tolerance ? atoi(time_tolerance) : 300; + + // Set cache expiration + int cache_timeout = get_cache_timeout(); + g_unified_cache.cache_expires = time(NULL) + cache_timeout; + g_unified_cache.cache_valid = 1; + + log_info("Unified configuration cache refreshed from database"); + return 0; +} + +// Get admin pubkey from cache (with automatic refresh) +const char* get_admin_pubkey_cached(void) { + pthread_mutex_lock(&g_unified_cache.cache_lock); + + // Check cache validity + if (!g_unified_cache.cache_valid || time(NULL) > g_unified_cache.cache_expires) { + refresh_unified_cache_from_table(); + } + + const char* result = g_unified_cache.admin_pubkey[0] ? g_unified_cache.admin_pubkey : NULL; + pthread_mutex_unlock(&g_unified_cache.cache_lock); + return result; +} + +// Get relay pubkey from cache (with automatic refresh) +const char* get_relay_pubkey_cached(void) { + pthread_mutex_lock(&g_unified_cache.cache_lock); + + // Check cache validity + if (!g_unified_cache.cache_valid || time(NULL) > g_unified_cache.cache_expires) { + refresh_unified_cache_from_table(); + } + + const char* result = g_unified_cache.relay_pubkey[0] ? g_unified_cache.relay_pubkey : NULL; + pthread_mutex_unlock(&g_unified_cache.cache_lock); + return result; +} + // ================================ // UTILITY FUNCTIONS // ================================ @@ -254,15 +378,16 @@ cJSON* load_config_event_from_database(const char* relay_pubkey) { sqlite3_stmt* stmt; int rc; - // If we have admin pubkey, query by it; otherwise find the most recent kind 33334 event - if (strlen(g_config_manager.admin_pubkey) > 0) { + // Try to get admin pubkey from cache, otherwise find the most recent kind 33334 event + const char* admin_pubkey = get_admin_pubkey_cached(); + if (admin_pubkey && strlen(admin_pubkey) > 0) { sql = "SELECT id, pubkey, created_at, kind, content, sig, tags FROM events WHERE kind = 33334 AND pubkey = ? ORDER BY created_at DESC LIMIT 1"; rc = sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL); if (rc != SQLITE_OK) { log_error("Failed to prepare configuration event query"); return NULL; } - sqlite3_bind_text(stmt, 1, g_config_manager.admin_pubkey, -1, SQLITE_STATIC); + sqlite3_bind_text(stmt, 1, admin_pubkey, -1, SQLITE_STATIC); } else { // During existing relay startup, we don't know the admin pubkey yet // Look for any kind 33334 configuration event (should only be one per relay) @@ -288,11 +413,8 @@ cJSON* load_config_event_from_database(const char* relay_pubkey) { cJSON_AddStringToObject(event, "content", (const char*)sqlite3_column_text(stmt, 4)); cJSON_AddStringToObject(event, "sig", (const char*)sqlite3_column_text(stmt, 5)); - // If we didn't have admin pubkey, store it now - if (strlen(g_config_manager.admin_pubkey) == 0) { - strncpy(g_config_manager.admin_pubkey, event_pubkey, sizeof(g_config_manager.admin_pubkey) - 1); - g_config_manager.admin_pubkey[sizeof(g_config_manager.admin_pubkey) - 1] = '\0'; - } + // If we didn't have admin pubkey in cache, we should update the cache + // Note: This will be handled by the cache refresh mechanism automatically // Parse tags JSON const char* tags_str = (const char*)sqlite3_column_text(stmt, 6); @@ -318,9 +440,28 @@ cJSON* load_config_event_from_database(const char* relay_pubkey) { // ================================ const char* get_config_value(const char* key) { - static char buffer[CONFIG_VALUE_MAX_LENGTH]; + if (!key) { + return NULL; + } - if (!key || !g_current_config) { + // Special fast path for frequently accessed keys via unified cache + if (strcmp(key, "admin_pubkey") == 0) { + return get_admin_pubkey_cached(); + } + if (strcmp(key, "relay_pubkey") == 0) { + return get_relay_pubkey_cached(); + } + + // For other keys, try config table first + const char* table_value = get_config_value_from_table(key); + if (table_value) { + return table_value; + } + + // Fallback to legacy event-based config for backward compatibility + // Use unified cache buffer instead of static buffer + + if (!g_current_config) { return NULL; } @@ -330,24 +471,30 @@ const char* get_config_value(const char* key) { return NULL; } + pthread_mutex_lock(&g_unified_cache.cache_lock); + cJSON* tag = NULL; cJSON_ArrayForEach(tag, tags) { if (cJSON_IsArray(tag) && cJSON_GetArraySize(tag) >= 2) { cJSON* tag_key = cJSON_GetArrayItem(tag, 0); cJSON* tag_value = cJSON_GetArrayItem(tag, 1); - if (tag_key && tag_value && + if (tag_key && tag_value && cJSON_IsString(tag_key) && cJSON_IsString(tag_value)) { if (strcmp(cJSON_GetStringValue(tag_key), key) == 0) { - strncpy(buffer, cJSON_GetStringValue(tag_value), sizeof(buffer) - 1); - buffer[sizeof(buffer) - 1] = '\0'; - return buffer; + strncpy(g_unified_cache.temp_buffer, cJSON_GetStringValue(tag_value), + sizeof(g_unified_cache.temp_buffer) - 1); + g_unified_cache.temp_buffer[sizeof(g_unified_cache.temp_buffer) - 1] = '\0'; + pthread_mutex_unlock(&g_unified_cache.cache_lock); + return g_unified_cache.temp_buffer; } } } } + pthread_mutex_unlock(&g_unified_cache.cache_lock); + return NULL; } @@ -491,14 +638,44 @@ int init_configuration_system(const char* config_dir_override, const char* confi log_info("Initializing event-based configuration system..."); - // Clear configuration manager state - memset(&g_config_manager, 0, sizeof(config_manager_t)); - g_config_manager.db = g_db; + // Initialize unified cache with proper structure initialization + pthread_mutex_lock(&g_unified_cache.cache_lock); - // For now, set empty paths for compatibility - g_config_manager.config_file_path[0] = '\0'; + // Clear the entire cache structure + memset(&g_unified_cache, 0, sizeof(g_unified_cache)); - log_success("Event-based configuration system initialized"); + // Reinitialize the mutex after memset + g_unified_cache.cache_lock = (pthread_mutex_t)PTHREAD_MUTEX_INITIALIZER; + + // Initialize basic cache state + g_unified_cache.cache_valid = 0; + g_unified_cache.cache_expires = 0; + + // Initialize relay_info structure with default values + strncpy(g_unified_cache.relay_info.software, "https://git.laantungir.net/laantungir/c-relay.git", + sizeof(g_unified_cache.relay_info.software) - 1); + strncpy(g_unified_cache.relay_info.version, "0.2.0", + sizeof(g_unified_cache.relay_info.version) - 1); + + // Initialize pow_config structure with defaults + g_unified_cache.pow_config.enabled = 1; + g_unified_cache.pow_config.min_pow_difficulty = 0; + g_unified_cache.pow_config.validation_flags = 1; // NOSTR_POW_VALIDATE_BASIC + g_unified_cache.pow_config.require_nonce_tag = 0; + g_unified_cache.pow_config.reject_lower_targets = 0; + g_unified_cache.pow_config.strict_format = 0; + g_unified_cache.pow_config.anti_spam_mode = 0; + + // Initialize expiration_config structure with defaults + g_unified_cache.expiration_config.enabled = 1; + g_unified_cache.expiration_config.strict_mode = 1; + g_unified_cache.expiration_config.filter_responses = 1; + g_unified_cache.expiration_config.delete_expired = 0; + g_unified_cache.expiration_config.grace_period = 1; + + pthread_mutex_unlock(&g_unified_cache.cache_lock); + + log_success("Event-based configuration system initialized with unified cache structures"); return 0; } @@ -515,8 +692,38 @@ void cleanup_configuration_system(void) { g_pending_config_event = NULL; } - memset(&g_config_manager, 0, sizeof(config_manager_t)); - log_success("Configuration system cleaned up"); + // Clear unified cache with proper cleanup of JSON objects + pthread_mutex_lock(&g_unified_cache.cache_lock); + + // Clean up relay_info JSON objects if they exist + if (g_unified_cache.relay_info.supported_nips) { + cJSON_Delete(g_unified_cache.relay_info.supported_nips); + } + if (g_unified_cache.relay_info.limitation) { + cJSON_Delete(g_unified_cache.relay_info.limitation); + } + if (g_unified_cache.relay_info.retention) { + cJSON_Delete(g_unified_cache.relay_info.retention); + } + if (g_unified_cache.relay_info.relay_countries) { + cJSON_Delete(g_unified_cache.relay_info.relay_countries); + } + if (g_unified_cache.relay_info.language_tags) { + cJSON_Delete(g_unified_cache.relay_info.language_tags); + } + if (g_unified_cache.relay_info.tags) { + cJSON_Delete(g_unified_cache.relay_info.tags); + } + if (g_unified_cache.relay_info.fees) { + cJSON_Delete(g_unified_cache.relay_info.fees); + } + + // Clear the entire cache structure + memset(&g_unified_cache, 0, sizeof(g_unified_cache)); + g_unified_cache.cache_lock = (pthread_mutex_t)PTHREAD_MUTEX_INITIALIZER; + + pthread_mutex_unlock(&g_unified_cache.cache_lock); + log_success("Configuration system cleaned up with proper JSON cleanup"); } int set_database_config(const char* key, const char* value, const char* changed_by) { @@ -832,11 +1039,13 @@ int first_time_startup_sequence(const cli_options_t* cli_options) { } nostr_bytes_to_hex(relay_pubkey_bytes, 32, relay_pubkey); - // 3. Store keys in global config manager - strncpy(g_config_manager.admin_pubkey, admin_pubkey, sizeof(g_config_manager.admin_pubkey) - 1); - g_config_manager.admin_pubkey[sizeof(g_config_manager.admin_pubkey) - 1] = '\0'; - strncpy(g_config_manager.relay_pubkey, relay_pubkey, sizeof(g_config_manager.relay_pubkey) - 1); - g_config_manager.relay_pubkey[sizeof(g_config_manager.relay_pubkey) - 1] = '\0'; + // 3. Store keys in unified cache (will be added to database after init) + pthread_mutex_lock(&g_unified_cache.cache_lock); + strncpy(g_unified_cache.admin_pubkey, admin_pubkey, sizeof(g_unified_cache.admin_pubkey) - 1); + g_unified_cache.admin_pubkey[sizeof(g_unified_cache.admin_pubkey) - 1] = '\0'; + strncpy(g_unified_cache.relay_pubkey, relay_pubkey, sizeof(g_unified_cache.relay_pubkey) - 1); + g_unified_cache.relay_pubkey[sizeof(g_unified_cache.relay_pubkey) - 1] = '\0'; + pthread_mutex_unlock(&g_unified_cache.cache_lock); // 4. Create database with relay pubkey name if (create_database_with_relay_pubkey(relay_pubkey) != 0) { @@ -904,8 +1113,11 @@ int startup_existing_relay(const char* relay_pubkey) { log_info("Starting existing relay..."); printf(" Relay pubkey: %s\n", relay_pubkey); - // Store relay pubkey in global config manager - strncpy(g_config_manager.relay_pubkey, relay_pubkey, sizeof(g_config_manager.relay_pubkey) - 1); + // Store relay pubkey in unified cache + pthread_mutex_lock(&g_unified_cache.cache_lock); + strncpy(g_unified_cache.relay_pubkey, relay_pubkey, sizeof(g_unified_cache.relay_pubkey) - 1); + g_unified_cache.relay_pubkey[sizeof(g_unified_cache.relay_pubkey) - 1] = '\0'; + pthread_mutex_unlock(&g_unified_cache.cache_lock); // Set database path char* db_name = get_database_name_from_relay_pubkey(relay_pubkey); @@ -1408,8 +1620,9 @@ int process_configuration_event(const cJSON* event) { // Verify it's from the admin const char* event_pubkey = cJSON_GetStringValue(pubkey_obj); - if (strlen(g_config_manager.admin_pubkey) > 0) { - if (strcmp(event_pubkey, g_config_manager.admin_pubkey) != 0) { + const char* admin_pubkey = get_admin_pubkey_cached(); + if (admin_pubkey && strlen(admin_pubkey) > 0) { + if (strcmp(event_pubkey, admin_pubkey) != 0) { log_error("Configuration event not from authorized admin"); return -1; } @@ -1580,11 +1793,18 @@ int apply_configuration_from_event(const cJSON* event) { // Update cached configuration g_current_config = cJSON_Duplicate(event, 1); - // Extract admin pubkey if not already set + // Extract admin pubkey if not already in cache cJSON* pubkey_obj = cJSON_GetObjectItem(event, "pubkey"); - if (pubkey_obj && strlen(g_config_manager.admin_pubkey) == 0) { - strncpy(g_config_manager.admin_pubkey, cJSON_GetStringValue(pubkey_obj), - sizeof(g_config_manager.admin_pubkey) - 1); + if (pubkey_obj) { + const char* event_pubkey = cJSON_GetStringValue(pubkey_obj); + const char* cached_admin_pubkey = get_admin_pubkey_cached(); + if (!cached_admin_pubkey || strlen(cached_admin_pubkey) == 0) { + // Update cache with admin pubkey from event + pthread_mutex_lock(&g_unified_cache.cache_lock); + strncpy(g_unified_cache.admin_pubkey, event_pubkey, sizeof(g_unified_cache.admin_pubkey) - 1); + g_unified_cache.admin_pubkey[sizeof(g_unified_cache.admin_pubkey) - 1] = '\0'; + pthread_mutex_unlock(&g_unified_cache.cache_lock); + } } // Apply runtime configuration changes @@ -1651,15 +1871,17 @@ const char* get_config_value_from_table(const char* key) { sqlite3_bind_text(stmt, 1, key, -1, SQLITE_STATIC); - static char config_value_buffer[CONFIG_VALUE_MAX_LENGTH]; const char* result = NULL; if (sqlite3_step(stmt) == SQLITE_ROW) { const char* value = (char*)sqlite3_column_text(stmt, 0); if (value) { - strncpy(config_value_buffer, value, sizeof(config_value_buffer) - 1); - config_value_buffer[sizeof(config_value_buffer) - 1] = '\0'; - result = config_value_buffer; + // Use unified cache buffer with thread safety + pthread_mutex_lock(&g_unified_cache.cache_lock); + strncpy(g_unified_cache.temp_buffer, value, sizeof(g_unified_cache.temp_buffer) - 1); + g_unified_cache.temp_buffer[sizeof(g_unified_cache.temp_buffer) - 1] = '\0'; + result = g_unified_cache.temp_buffer; + pthread_mutex_unlock(&g_unified_cache.cache_lock); } } @@ -1783,6 +2005,52 @@ int populate_default_config_values(void) { return 0; } +// Add dynamically generated pubkeys to config table +int add_pubkeys_to_config_table(void) { + if (!g_db) { + log_error("Database not available for pubkey storage"); + return -1; + } + + log_info("Adding dynamically generated pubkeys to config table..."); + + // Get the pubkeys directly from unified cache (not through cached accessors to avoid circular dependency) + pthread_mutex_lock(&g_unified_cache.cache_lock); + const char* admin_pubkey = g_unified_cache.admin_pubkey[0] ? g_unified_cache.admin_pubkey : NULL; + const char* relay_pubkey = g_unified_cache.relay_pubkey[0] ? g_unified_cache.relay_pubkey : NULL; + pthread_mutex_unlock(&g_unified_cache.cache_lock); + + if (!admin_pubkey || strlen(admin_pubkey) != 64) { + log_error("Admin pubkey not available or invalid for config table storage"); + return -1; + } + + if (!relay_pubkey || strlen(relay_pubkey) != 64) { + log_error("Relay pubkey not available or invalid for config table storage"); + return -1; + } + + // Store admin pubkey in config table + if (set_config_value_in_table("admin_pubkey", admin_pubkey, "string", + "Administrator public key", "authentication", 0) != 0) { + log_error("Failed to store admin_pubkey in config table"); + return -1; + } + + // Store relay pubkey in config table + if (set_config_value_in_table("relay_pubkey", relay_pubkey, "string", + "Relay public key", "relay", 0) != 0) { + log_error("Failed to store relay_pubkey in config table"); + return -1; + } + + log_success("Dynamically generated pubkeys added to config table"); + printf(" Admin pubkey: %s\n", admin_pubkey); + printf(" Relay pubkey: %s\n", relay_pubkey); + + return 0; +} + // ================================ // ADMIN EVENT PROCESSING FUNCTIONS // ================================ @@ -2028,17 +2296,24 @@ int remove_auth_rule_from_config(const char* rule_type, const char* pattern_type // Invalidate configuration cache void invalidate_config_cache(void) { - // For now, just log that cache was invalidated - // In a full implementation, this would clear any cached config values - log_info("Configuration cache invalidated"); + pthread_mutex_lock(&g_unified_cache.cache_lock); + g_unified_cache.cache_valid = 0; + g_unified_cache.cache_expires = 0; + pthread_mutex_unlock(&g_unified_cache.cache_lock); + log_info("Unified configuration cache invalidated"); } // Reload configuration from table int reload_config_from_table(void) { - // For now, just log that config was reloaded - // In a full implementation, this would reload all cached values from the table - log_info("Configuration reloaded from table"); - return 0; + // Trigger a cache refresh by calling the refresh function directly + int result = refresh_unified_cache_from_table(); + + if (result == 0) { + log_info("Configuration reloaded from table"); + } else { + log_error("Failed to reload configuration from table"); + } + return result; } // ================================ @@ -2101,8 +2376,11 @@ int is_config_table_ready(void) { int initialize_config_system_with_migration(void) { log_info("Initializing configuration system with migration support..."); - // Initialize config manager - memset(&g_config_manager, 0, sizeof(g_config_manager)); + // Initialize unified cache and migration status + pthread_mutex_lock(&g_unified_cache.cache_lock); + g_unified_cache.cache_valid = 0; + g_unified_cache.cache_expires = 0; + pthread_mutex_unlock(&g_unified_cache.cache_lock); memset(&g_migration_status, 0, sizeof(g_migration_status)); // For new installations, config table should already exist from embedded schema @@ -2254,7 +2532,8 @@ int migrate_config_from_events_to_table(void) { log_info("Migrating configuration from events to config table..."); // Load the most recent configuration event from database - cJSON* config_event = load_config_event_from_database(g_config_manager.relay_pubkey); + const char* relay_pubkey = get_relay_pubkey_cached(); + cJSON* config_event = load_config_event_from_database(relay_pubkey); if (!config_event) { log_info("No existing configuration event found - migration not needed"); return 0; @@ -2382,4 +2661,191 @@ int process_startup_config_event_with_fallback(const cJSON* event) { log_error("Startup configuration processing failed even after populating defaults"); return -1; +} + +// ================================ +// DYNAMIC EVENT GENERATION FROM CONFIG TABLE +// ================================ + +// Generate synthetic kind 33334 configuration event from current config table data +cJSON* generate_config_event_from_table(void) { + if (!g_db) { + log_error("Database not available for config event generation"); + return NULL; + } + + log_info("Generating synthetic kind 33334 event from config table..."); + + // Get relay pubkey for event generation + const char* relay_pubkey = get_config_value("relay_pubkey"); + if (!relay_pubkey || strlen(relay_pubkey) != 64) { + // Try to get from unified cache + relay_pubkey = get_relay_pubkey_cached(); + if (!relay_pubkey || strlen(relay_pubkey) != 64) { + log_error("Relay pubkey not available for config event generation"); + return NULL; + } + } + + // Create the event structure + cJSON* event = cJSON_CreateObject(); + if (!event) { + log_error("Failed to create config event object"); + return NULL; + } + + // Set basic event fields - we'll generate a synthetic event + cJSON_AddStringToObject(event, "id", "synthetic_config_event_id"); + cJSON_AddStringToObject(event, "pubkey", relay_pubkey); // Use relay pubkey as event author + cJSON_AddNumberToObject(event, "created_at", (double)time(NULL)); + cJSON_AddNumberToObject(event, "kind", 33334); + cJSON_AddStringToObject(event, "content", "C Nostr Relay Configuration"); + cJSON_AddStringToObject(event, "sig", "synthetic_signature"); + + // Create tags array from config table + cJSON* tags = cJSON_CreateArray(); + if (!tags) { + log_error("Failed to create tags array for config event"); + cJSON_Delete(event); + return NULL; + } + + // Add d tag with relay pubkey (addressable event identifier) + cJSON* d_tag = cJSON_CreateArray(); + cJSON_AddItemToArray(d_tag, cJSON_CreateString("d")); + cJSON_AddItemToArray(d_tag, cJSON_CreateString(relay_pubkey)); + cJSON_AddItemToArray(tags, d_tag); + + // Query all configuration values from the config table + const char* sql = "SELECT key, value FROM config ORDER BY key"; + sqlite3_stmt* stmt; + + int rc = sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL); + if (rc != SQLITE_OK) { + log_error("Failed to prepare config query for event generation"); + cJSON_Delete(tags); + cJSON_Delete(event); + return NULL; + } + + int config_items_added = 0; + + // Add each config item as a tag + while (sqlite3_step(stmt) == SQLITE_ROW) { + const char* key = (const char*)sqlite3_column_text(stmt, 0); + const char* value = (const char*)sqlite3_column_text(stmt, 1); + + if (key && value) { + cJSON* config_tag = cJSON_CreateArray(); + cJSON_AddItemToArray(config_tag, cJSON_CreateString(key)); + cJSON_AddItemToArray(config_tag, cJSON_CreateString(value)); + cJSON_AddItemToArray(tags, config_tag); + config_items_added++; + } + } + + sqlite3_finalize(stmt); + + if (config_items_added == 0) { + log_warning("No configuration items found in config table for event generation"); + cJSON_Delete(tags); + cJSON_Delete(event); + return NULL; + } + + // Add tags to event + cJSON_AddItemToObject(event, "tags", tags); + + char success_msg[256]; + snprintf(success_msg, sizeof(success_msg), + "Generated synthetic kind 33334 event with %d configuration items", config_items_added); + log_success(success_msg); + + return event; +} + +// Check if a REQ filter requests kind 33334 events +int req_filter_requests_config_events(const cJSON* filter) { + if (!filter || !cJSON_IsObject(filter)) { + return 0; + } + + cJSON* kinds = cJSON_GetObjectItem(filter, "kinds"); + if (!kinds || !cJSON_IsArray(kinds)) { + return 0; + } + + // Check if kinds array contains 33334 + cJSON* kind_item = NULL; + cJSON_ArrayForEach(kind_item, kinds) { + if (cJSON_IsNumber(kind_item) && (int)cJSON_GetNumberValue(kind_item) == 33334) { + return 1; + } + } + + return 0; +} + +// Generate synthetic config event data for subscription (callback approach) +cJSON* generate_synthetic_config_event_for_subscription(const char* sub_id, const cJSON* filters) { + if (!sub_id || !filters) { + return NULL; + } + + // Check if any filter requests kind 33334 + int requests_config = 0; + + if (cJSON_IsArray(filters)) { + cJSON* filter = NULL; + cJSON_ArrayForEach(filter, filters) { + if (req_filter_requests_config_events(filter)) { + requests_config = 1; + break; + } + } + } else if (cJSON_IsObject(filters)) { + requests_config = req_filter_requests_config_events(filters); + } + + if (!requests_config) { + // No config events requested + return NULL; + } + + log_info("Generating synthetic kind 33334 event for subscription"); + + // Generate synthetic config event from table + cJSON* config_event = generate_config_event_from_table(); + if (!config_event) { + log_error("Failed to generate synthetic config event"); + return NULL; + } + + // Create EVENT message for the subscription + cJSON* event_msg = cJSON_CreateArray(); + cJSON_AddItemToArray(event_msg, cJSON_CreateString("EVENT")); + cJSON_AddItemToArray(event_msg, cJSON_CreateString(sub_id)); + cJSON_AddItemToArray(event_msg, config_event); + + log_success("Generated synthetic kind 33334 configuration event message"); + return event_msg; +} + +/** + * Generate a synthetic kind 33334 configuration event from config table data + * This allows WebSocket clients to fetch configuration via REQ messages + * Returns JSON string that must be freed by caller + */ +char* generate_config_event_json(void) { + // Use the existing cJSON function and convert to string + cJSON* event = generate_config_event_from_table(); + if (!event) { + return NULL; + } + + // Convert to JSON string + char* json_string = cJSON_Print(event); + cJSON_Delete(event); + + return json_string; } \ No newline at end of file diff --git a/src/config.h b/src/config.h index a1f3df6..56648e7 100644 --- a/src/config.h +++ b/src/config.h @@ -4,6 +4,7 @@ #include #include #include +#include // Configuration constants #define CONFIG_VALUE_MAX_LENGTH 1024 @@ -23,14 +24,71 @@ // Database path for event-based config extern char g_database_path[512]; -// Configuration manager structure +// Unified configuration cache structure (consolidates all caching systems) typedef struct { - sqlite3* db; - char relay_pubkey[65]; + // Critical keys (frequently accessed) char admin_pubkey[65]; - time_t last_config_check; - char config_file_path[512]; // Temporary for compatibility -} config_manager_t; + char relay_pubkey[65]; + + // Auth config (from request_validator) + int auth_required; + long max_file_size; + int admin_enabled; + int nip42_mode; + int nip42_challenge_timeout; + int nip42_time_tolerance; + + // Static buffer for config values (replaces static buffers in get_config_value functions) + char temp_buffer[CONFIG_VALUE_MAX_LENGTH]; + + // NIP-11 relay information (migrated from g_relay_info in main.c) + struct { + char name[RELAY_NAME_MAX_LENGTH]; + char description[RELAY_DESCRIPTION_MAX_LENGTH]; + char banner[RELAY_URL_MAX_LENGTH]; + char icon[RELAY_URL_MAX_LENGTH]; + char pubkey[RELAY_PUBKEY_MAX_LENGTH]; + char contact[RELAY_CONTACT_MAX_LENGTH]; + char software[RELAY_URL_MAX_LENGTH]; + char version[64]; + char privacy_policy[RELAY_URL_MAX_LENGTH]; + char terms_of_service[RELAY_URL_MAX_LENGTH]; + cJSON* supported_nips; + cJSON* limitation; + cJSON* retention; + cJSON* relay_countries; + cJSON* language_tags; + cJSON* tags; + char posting_policy[RELAY_URL_MAX_LENGTH]; + cJSON* fees; + char payments_url[RELAY_URL_MAX_LENGTH]; + } relay_info; + + // NIP-13 PoW configuration (migrated from g_pow_config in main.c) + struct { + int enabled; + int min_pow_difficulty; + int validation_flags; + int require_nonce_tag; + int reject_lower_targets; + int strict_format; + int anti_spam_mode; + } pow_config; + + // NIP-40 Expiration configuration (migrated from g_expiration_config in main.c) + struct { + int enabled; + int strict_mode; + int filter_responses; + int delete_expired; + long grace_period; + } expiration_config; + + // Cache management + time_t cache_expires; + int cache_valid; + pthread_mutex_t cache_lock; +} unified_config_cache_t; // Command line options structure for first-time startup typedef struct { @@ -39,8 +97,8 @@ typedef struct { char relay_privkey_override[65]; // Empty string = not set, 64-char hex = override } cli_options_t; -// Global configuration manager -extern config_manager_t g_config_manager; +// Global unified configuration cache +extern unified_config_cache_t g_unified_cache; // Core configuration functions (temporary compatibility) int init_configuration_system(const char* config_dir_override, const char* config_file_override); @@ -100,6 +158,7 @@ int set_config_value_in_table(const char* key, const char* value, const char* da const char* description, const char* category, int requires_restart); int update_config_in_table(const char* key, const char* value); int populate_default_config_values(void); +int add_pubkeys_to_config_table(void); // Admin event processing functions int process_admin_event_in_config(cJSON* event, char* error_message, size_t error_size); @@ -112,7 +171,10 @@ int add_auth_rule_from_config(const char* rule_type, const char* pattern_type, int remove_auth_rule_from_config(const char* rule_type, const char* pattern_type, const char* pattern_value); -// Configuration cache management +// Unified configuration cache management +void force_config_cache_refresh(void); +const char* get_admin_pubkey_cached(void); +const char* get_relay_pubkey_cached(void); void invalidate_config_cache(void); int reload_config_from_table(void); @@ -129,4 +191,10 @@ int populate_config_table_from_event(const cJSON* event); int process_startup_config_event(const cJSON* event); int process_startup_config_event_with_fallback(const cJSON* event); +// Dynamic event generation functions for WebSocket configuration fetching +cJSON* generate_config_event_from_table(void); +int req_filter_requests_config_events(const cJSON* filter); +cJSON* generate_synthetic_config_event_for_subscription(const char* sub_id, const cJSON* filters); +char* generate_config_event_json(void); + #endif /* CONFIG_H */ \ No newline at end of file diff --git a/src/main.c b/src/main.c index 091ec3d..4d349e3 100644 --- a/src/main.c +++ b/src/main.c @@ -68,8 +68,8 @@ struct relay_info { char payments_url[RELAY_URL_MAX_LENGTH]; }; -// Global relay information instance -static struct relay_info g_relay_info = {0}; +// Global relay information instance moved to unified cache +// static struct relay_info g_relay_info = {0}; // REMOVED - now in g_unified_cache.relay_info // NIP-13 PoW configuration structure struct pow_config { @@ -698,7 +698,12 @@ int broadcast_event_to_subscriptions(cJSON* event) { } // Check if event is expired and should not be broadcast (NIP-40) - if (g_expiration_config.enabled && g_expiration_config.filter_responses) { + pthread_mutex_lock(&g_unified_cache.cache_lock); + int expiration_enabled = g_unified_cache.expiration_config.enabled; + int filter_responses = g_unified_cache.expiration_config.filter_responses; + pthread_mutex_unlock(&g_unified_cache.cache_lock); + + if (expiration_enabled && filter_responses) { time_t current_time = time(NULL); if (is_event_expired(event, current_time)) { char debug_msg[256]; @@ -1480,131 +1485,147 @@ int mark_event_as_deleted(const char* event_id, const char* deletion_event_id, c // Initialize relay information using configuration system void init_relay_info() { - // Load relay information from configuration system + // Get all config values first (without holding mutex to avoid deadlock) const char* relay_name = get_config_value("relay_name"); - if (relay_name) { - strncpy(g_relay_info.name, relay_name, sizeof(g_relay_info.name) - 1); - } else { - strncpy(g_relay_info.name, "C Nostr Relay", sizeof(g_relay_info.name) - 1); - } - const char* relay_description = get_config_value("relay_description"); - if (relay_description) { - strncpy(g_relay_info.description, relay_description, sizeof(g_relay_info.description) - 1); - } else { - strncpy(g_relay_info.description, "A high-performance Nostr relay implemented in C with SQLite storage", sizeof(g_relay_info.description) - 1); - } - const char* relay_software = get_config_value("relay_software"); - if (relay_software) { - strncpy(g_relay_info.software, relay_software, sizeof(g_relay_info.software) - 1); - } else { - strncpy(g_relay_info.software, "https://git.laantungir.net/laantungir/c-relay.git", sizeof(g_relay_info.software) - 1); - } - const char* relay_version = get_config_value("relay_version"); - if (relay_version) { - strncpy(g_relay_info.version, relay_version, sizeof(g_relay_info.version) - 1); - } else { - strncpy(g_relay_info.version, "0.2.0", sizeof(g_relay_info.version) - 1); - } - - // Load optional fields const char* relay_contact = get_config_value("relay_contact"); - if (relay_contact) { - strncpy(g_relay_info.contact, relay_contact, sizeof(g_relay_info.contact) - 1); + const char* relay_pubkey = get_config_value("relay_pubkey"); + + // Get config values for limitations + int max_message_length = get_config_int("max_message_length", 16384); + int max_subscriptions_per_client = get_config_int("max_subscriptions_per_client", 20); + int max_limit = get_config_int("max_limit", 5000); + int max_event_tags = get_config_int("max_event_tags", 100); + int max_content_length = get_config_int("max_content_length", 8196); + int default_limit = get_config_int("default_limit", 500); + int admin_enabled = get_config_bool("admin_enabled", 0); + + pthread_mutex_lock(&g_unified_cache.cache_lock); + + // Update relay information fields + if (relay_name) { + strncpy(g_unified_cache.relay_info.name, relay_name, sizeof(g_unified_cache.relay_info.name) - 1); + } else { + strncpy(g_unified_cache.relay_info.name, "C Nostr Relay", sizeof(g_unified_cache.relay_info.name) - 1); + } + + if (relay_description) { + strncpy(g_unified_cache.relay_info.description, relay_description, sizeof(g_unified_cache.relay_info.description) - 1); + } else { + strncpy(g_unified_cache.relay_info.description, "A high-performance Nostr relay implemented in C with SQLite storage", sizeof(g_unified_cache.relay_info.description) - 1); + } + + if (relay_software) { + strncpy(g_unified_cache.relay_info.software, relay_software, sizeof(g_unified_cache.relay_info.software) - 1); + } else { + strncpy(g_unified_cache.relay_info.software, "https://git.laantungir.net/laantungir/c-relay.git", sizeof(g_unified_cache.relay_info.software) - 1); + } + + if (relay_version) { + strncpy(g_unified_cache.relay_info.version, relay_version, sizeof(g_unified_cache.relay_info.version) - 1); + } else { + strncpy(g_unified_cache.relay_info.version, "0.2.0", sizeof(g_unified_cache.relay_info.version) - 1); + } + + if (relay_contact) { + strncpy(g_unified_cache.relay_info.contact, relay_contact, sizeof(g_unified_cache.relay_info.contact) - 1); } - const char* relay_pubkey = get_config_value("relay_pubkey"); if (relay_pubkey) { - strncpy(g_relay_info.pubkey, relay_pubkey, sizeof(g_relay_info.pubkey) - 1); + strncpy(g_unified_cache.relay_info.pubkey, relay_pubkey, sizeof(g_unified_cache.relay_info.pubkey) - 1); } // Initialize supported NIPs array - g_relay_info.supported_nips = cJSON_CreateArray(); - if (g_relay_info.supported_nips) { - cJSON_AddItemToArray(g_relay_info.supported_nips, cJSON_CreateNumber(1)); // NIP-01: Basic protocol - cJSON_AddItemToArray(g_relay_info.supported_nips, cJSON_CreateNumber(9)); // NIP-09: Event deletion - cJSON_AddItemToArray(g_relay_info.supported_nips, cJSON_CreateNumber(11)); // NIP-11: Relay information - cJSON_AddItemToArray(g_relay_info.supported_nips, cJSON_CreateNumber(13)); // NIP-13: Proof of Work - cJSON_AddItemToArray(g_relay_info.supported_nips, cJSON_CreateNumber(15)); // NIP-15: EOSE - cJSON_AddItemToArray(g_relay_info.supported_nips, cJSON_CreateNumber(20)); // NIP-20: Command results - cJSON_AddItemToArray(g_relay_info.supported_nips, cJSON_CreateNumber(40)); // NIP-40: Expiration Timestamp - cJSON_AddItemToArray(g_relay_info.supported_nips, cJSON_CreateNumber(42)); // NIP-42: Authentication + g_unified_cache.relay_info.supported_nips = cJSON_CreateArray(); + if (g_unified_cache.relay_info.supported_nips) { + cJSON_AddItemToArray(g_unified_cache.relay_info.supported_nips, cJSON_CreateNumber(1)); // NIP-01: Basic protocol + cJSON_AddItemToArray(g_unified_cache.relay_info.supported_nips, cJSON_CreateNumber(9)); // NIP-09: Event deletion + cJSON_AddItemToArray(g_unified_cache.relay_info.supported_nips, cJSON_CreateNumber(11)); // NIP-11: Relay information + cJSON_AddItemToArray(g_unified_cache.relay_info.supported_nips, cJSON_CreateNumber(13)); // NIP-13: Proof of Work + cJSON_AddItemToArray(g_unified_cache.relay_info.supported_nips, cJSON_CreateNumber(15)); // NIP-15: EOSE + cJSON_AddItemToArray(g_unified_cache.relay_info.supported_nips, cJSON_CreateNumber(20)); // NIP-20: Command results + cJSON_AddItemToArray(g_unified_cache.relay_info.supported_nips, cJSON_CreateNumber(40)); // NIP-40: Expiration Timestamp + cJSON_AddItemToArray(g_unified_cache.relay_info.supported_nips, cJSON_CreateNumber(42)); // NIP-42: Authentication } // Initialize server limitations using configuration - g_relay_info.limitation = cJSON_CreateObject(); - if (g_relay_info.limitation) { - cJSON_AddNumberToObject(g_relay_info.limitation, "max_message_length", get_config_int("max_message_length", 16384)); - cJSON_AddNumberToObject(g_relay_info.limitation, "max_subscriptions", get_config_int("max_subscriptions_per_client", 20)); - cJSON_AddNumberToObject(g_relay_info.limitation, "max_limit", get_config_int("max_limit", 5000)); - cJSON_AddNumberToObject(g_relay_info.limitation, "max_subid_length", SUBSCRIPTION_ID_MAX_LENGTH); - cJSON_AddNumberToObject(g_relay_info.limitation, "max_event_tags", get_config_int("max_event_tags", 100)); - cJSON_AddNumberToObject(g_relay_info.limitation, "max_content_length", get_config_int("max_content_length", 8196)); - cJSON_AddNumberToObject(g_relay_info.limitation, "min_pow_difficulty", g_pow_config.min_pow_difficulty); - cJSON_AddBoolToObject(g_relay_info.limitation, "auth_required", get_config_bool("admin_enabled", 0) ? cJSON_True : cJSON_False); - cJSON_AddBoolToObject(g_relay_info.limitation, "payment_required", cJSON_False); - cJSON_AddBoolToObject(g_relay_info.limitation, "restricted_writes", cJSON_False); - cJSON_AddNumberToObject(g_relay_info.limitation, "created_at_lower_limit", 0); - cJSON_AddNumberToObject(g_relay_info.limitation, "created_at_upper_limit", 2147483647); - cJSON_AddNumberToObject(g_relay_info.limitation, "default_limit", get_config_int("default_limit", 500)); + g_unified_cache.relay_info.limitation = cJSON_CreateObject(); + if (g_unified_cache.relay_info.limitation) { + cJSON_AddNumberToObject(g_unified_cache.relay_info.limitation, "max_message_length", max_message_length); + cJSON_AddNumberToObject(g_unified_cache.relay_info.limitation, "max_subscriptions", max_subscriptions_per_client); + cJSON_AddNumberToObject(g_unified_cache.relay_info.limitation, "max_limit", max_limit); + cJSON_AddNumberToObject(g_unified_cache.relay_info.limitation, "max_subid_length", SUBSCRIPTION_ID_MAX_LENGTH); + cJSON_AddNumberToObject(g_unified_cache.relay_info.limitation, "max_event_tags", max_event_tags); + cJSON_AddNumberToObject(g_unified_cache.relay_info.limitation, "max_content_length", max_content_length); + cJSON_AddNumberToObject(g_unified_cache.relay_info.limitation, "min_pow_difficulty", g_unified_cache.pow_config.min_pow_difficulty); + cJSON_AddBoolToObject(g_unified_cache.relay_info.limitation, "auth_required", admin_enabled ? cJSON_True : cJSON_False); + cJSON_AddBoolToObject(g_unified_cache.relay_info.limitation, "payment_required", cJSON_False); + cJSON_AddBoolToObject(g_unified_cache.relay_info.limitation, "restricted_writes", cJSON_False); + cJSON_AddNumberToObject(g_unified_cache.relay_info.limitation, "created_at_lower_limit", 0); + cJSON_AddNumberToObject(g_unified_cache.relay_info.limitation, "created_at_upper_limit", 2147483647); + cJSON_AddNumberToObject(g_unified_cache.relay_info.limitation, "default_limit", default_limit); } // Initialize empty retention policies (can be configured later) - g_relay_info.retention = cJSON_CreateArray(); + g_unified_cache.relay_info.retention = cJSON_CreateArray(); // Initialize language tags - set to global for now - g_relay_info.language_tags = cJSON_CreateArray(); - if (g_relay_info.language_tags) { - cJSON_AddItemToArray(g_relay_info.language_tags, cJSON_CreateString("*")); + g_unified_cache.relay_info.language_tags = cJSON_CreateArray(); + if (g_unified_cache.relay_info.language_tags) { + cJSON_AddItemToArray(g_unified_cache.relay_info.language_tags, cJSON_CreateString("*")); } // Initialize relay countries - set to global for now - g_relay_info.relay_countries = cJSON_CreateArray(); - if (g_relay_info.relay_countries) { - cJSON_AddItemToArray(g_relay_info.relay_countries, cJSON_CreateString("*")); + g_unified_cache.relay_info.relay_countries = cJSON_CreateArray(); + if (g_unified_cache.relay_info.relay_countries) { + cJSON_AddItemToArray(g_unified_cache.relay_info.relay_countries, cJSON_CreateString("*")); } // Initialize content tags as empty array - g_relay_info.tags = cJSON_CreateArray(); + g_unified_cache.relay_info.tags = cJSON_CreateArray(); // Initialize fees as empty object (no payment required by default) - g_relay_info.fees = cJSON_CreateObject(); + g_unified_cache.relay_info.fees = cJSON_CreateObject(); + + pthread_mutex_unlock(&g_unified_cache.cache_lock); log_success("Relay information initialized with default values"); } // Clean up relay information JSON objects void cleanup_relay_info() { - if (g_relay_info.supported_nips) { - cJSON_Delete(g_relay_info.supported_nips); - g_relay_info.supported_nips = NULL; + pthread_mutex_lock(&g_unified_cache.cache_lock); + if (g_unified_cache.relay_info.supported_nips) { + cJSON_Delete(g_unified_cache.relay_info.supported_nips); + g_unified_cache.relay_info.supported_nips = NULL; } - if (g_relay_info.limitation) { - cJSON_Delete(g_relay_info.limitation); - g_relay_info.limitation = NULL; + if (g_unified_cache.relay_info.limitation) { + cJSON_Delete(g_unified_cache.relay_info.limitation); + g_unified_cache.relay_info.limitation = NULL; } - if (g_relay_info.retention) { - cJSON_Delete(g_relay_info.retention); - g_relay_info.retention = NULL; + if (g_unified_cache.relay_info.retention) { + cJSON_Delete(g_unified_cache.relay_info.retention); + g_unified_cache.relay_info.retention = NULL; } - if (g_relay_info.language_tags) { - cJSON_Delete(g_relay_info.language_tags); - g_relay_info.language_tags = NULL; + if (g_unified_cache.relay_info.language_tags) { + cJSON_Delete(g_unified_cache.relay_info.language_tags); + g_unified_cache.relay_info.language_tags = NULL; } - if (g_relay_info.relay_countries) { - cJSON_Delete(g_relay_info.relay_countries); - g_relay_info.relay_countries = NULL; + if (g_unified_cache.relay_info.relay_countries) { + cJSON_Delete(g_unified_cache.relay_info.relay_countries); + g_unified_cache.relay_info.relay_countries = NULL; } - if (g_relay_info.tags) { - cJSON_Delete(g_relay_info.tags); - g_relay_info.tags = NULL; + if (g_unified_cache.relay_info.tags) { + cJSON_Delete(g_unified_cache.relay_info.tags); + g_unified_cache.relay_info.tags = NULL; } - if (g_relay_info.fees) { - cJSON_Delete(g_relay_info.fees); - g_relay_info.fees = NULL; + if (g_unified_cache.relay_info.fees) { + cJSON_Delete(g_unified_cache.relay_info.fees); + g_unified_cache.relay_info.fees = NULL; } + pthread_mutex_unlock(&g_unified_cache.cache_lock); } // Generate NIP-11 compliant JSON document @@ -1615,79 +1636,83 @@ cJSON* generate_relay_info_json() { return NULL; } + pthread_mutex_lock(&g_unified_cache.cache_lock); + // Add basic relay information - if (strlen(g_relay_info.name) > 0) { - cJSON_AddStringToObject(info, "name", g_relay_info.name); + if (strlen(g_unified_cache.relay_info.name) > 0) { + cJSON_AddStringToObject(info, "name", g_unified_cache.relay_info.name); } - if (strlen(g_relay_info.description) > 0) { - cJSON_AddStringToObject(info, "description", g_relay_info.description); + if (strlen(g_unified_cache.relay_info.description) > 0) { + cJSON_AddStringToObject(info, "description", g_unified_cache.relay_info.description); } - if (strlen(g_relay_info.banner) > 0) { - cJSON_AddStringToObject(info, "banner", g_relay_info.banner); + if (strlen(g_unified_cache.relay_info.banner) > 0) { + cJSON_AddStringToObject(info, "banner", g_unified_cache.relay_info.banner); } - if (strlen(g_relay_info.icon) > 0) { - cJSON_AddStringToObject(info, "icon", g_relay_info.icon); + if (strlen(g_unified_cache.relay_info.icon) > 0) { + cJSON_AddStringToObject(info, "icon", g_unified_cache.relay_info.icon); } - if (strlen(g_relay_info.pubkey) > 0) { - cJSON_AddStringToObject(info, "pubkey", g_relay_info.pubkey); + if (strlen(g_unified_cache.relay_info.pubkey) > 0) { + cJSON_AddStringToObject(info, "pubkey", g_unified_cache.relay_info.pubkey); } - if (strlen(g_relay_info.contact) > 0) { - cJSON_AddStringToObject(info, "contact", g_relay_info.contact); + if (strlen(g_unified_cache.relay_info.contact) > 0) { + cJSON_AddStringToObject(info, "contact", g_unified_cache.relay_info.contact); } // Add supported NIPs - if (g_relay_info.supported_nips) { - cJSON_AddItemToObject(info, "supported_nips", cJSON_Duplicate(g_relay_info.supported_nips, 1)); + if (g_unified_cache.relay_info.supported_nips) { + cJSON_AddItemToObject(info, "supported_nips", cJSON_Duplicate(g_unified_cache.relay_info.supported_nips, 1)); } // Add software information - if (strlen(g_relay_info.software) > 0) { - cJSON_AddStringToObject(info, "software", g_relay_info.software); + if (strlen(g_unified_cache.relay_info.software) > 0) { + cJSON_AddStringToObject(info, "software", g_unified_cache.relay_info.software); } - if (strlen(g_relay_info.version) > 0) { - cJSON_AddStringToObject(info, "version", g_relay_info.version); + if (strlen(g_unified_cache.relay_info.version) > 0) { + cJSON_AddStringToObject(info, "version", g_unified_cache.relay_info.version); } // Add policies - if (strlen(g_relay_info.privacy_policy) > 0) { - cJSON_AddStringToObject(info, "privacy_policy", g_relay_info.privacy_policy); + if (strlen(g_unified_cache.relay_info.privacy_policy) > 0) { + cJSON_AddStringToObject(info, "privacy_policy", g_unified_cache.relay_info.privacy_policy); } - if (strlen(g_relay_info.terms_of_service) > 0) { - cJSON_AddStringToObject(info, "terms_of_service", g_relay_info.terms_of_service); + if (strlen(g_unified_cache.relay_info.terms_of_service) > 0) { + cJSON_AddStringToObject(info, "terms_of_service", g_unified_cache.relay_info.terms_of_service); } - if (strlen(g_relay_info.posting_policy) > 0) { - cJSON_AddStringToObject(info, "posting_policy", g_relay_info.posting_policy); + if (strlen(g_unified_cache.relay_info.posting_policy) > 0) { + cJSON_AddStringToObject(info, "posting_policy", g_unified_cache.relay_info.posting_policy); } // Add server limitations - if (g_relay_info.limitation) { - cJSON_AddItemToObject(info, "limitation", cJSON_Duplicate(g_relay_info.limitation, 1)); + if (g_unified_cache.relay_info.limitation) { + cJSON_AddItemToObject(info, "limitation", cJSON_Duplicate(g_unified_cache.relay_info.limitation, 1)); } // Add retention policies if configured - if (g_relay_info.retention && cJSON_GetArraySize(g_relay_info.retention) > 0) { - cJSON_AddItemToObject(info, "retention", cJSON_Duplicate(g_relay_info.retention, 1)); + if (g_unified_cache.relay_info.retention && cJSON_GetArraySize(g_unified_cache.relay_info.retention) > 0) { + cJSON_AddItemToObject(info, "retention", cJSON_Duplicate(g_unified_cache.relay_info.retention, 1)); } // Add geographical and language information - if (g_relay_info.relay_countries) { - cJSON_AddItemToObject(info, "relay_countries", cJSON_Duplicate(g_relay_info.relay_countries, 1)); + if (g_unified_cache.relay_info.relay_countries) { + cJSON_AddItemToObject(info, "relay_countries", cJSON_Duplicate(g_unified_cache.relay_info.relay_countries, 1)); } - if (g_relay_info.language_tags) { - cJSON_AddItemToObject(info, "language_tags", cJSON_Duplicate(g_relay_info.language_tags, 1)); + if (g_unified_cache.relay_info.language_tags) { + cJSON_AddItemToObject(info, "language_tags", cJSON_Duplicate(g_unified_cache.relay_info.language_tags, 1)); } - if (g_relay_info.tags && cJSON_GetArraySize(g_relay_info.tags) > 0) { - cJSON_AddItemToObject(info, "tags", cJSON_Duplicate(g_relay_info.tags, 1)); + if (g_unified_cache.relay_info.tags && cJSON_GetArraySize(g_unified_cache.relay_info.tags) > 0) { + cJSON_AddItemToObject(info, "tags", cJSON_Duplicate(g_unified_cache.relay_info.tags, 1)); } // Add payment information if configured - if (strlen(g_relay_info.payments_url) > 0) { - cJSON_AddStringToObject(info, "payments_url", g_relay_info.payments_url); + if (strlen(g_unified_cache.relay_info.payments_url) > 0) { + cJSON_AddStringToObject(info, "payments_url", g_unified_cache.relay_info.payments_url); } - if (g_relay_info.fees && cJSON_GetObjectItem(g_relay_info.fees, "admission")) { - cJSON_AddItemToObject(info, "fees", cJSON_Duplicate(g_relay_info.fees, 1)); + if (g_unified_cache.relay_info.fees && cJSON_GetObjectItem(g_unified_cache.relay_info.fees, "admission")) { + cJSON_AddItemToObject(info, "fees", cJSON_Duplicate(g_unified_cache.relay_info.fees, 1)); } + pthread_mutex_unlock(&g_unified_cache.cache_lock); + return info; } @@ -1865,34 +1890,40 @@ int handle_nip11_http_request(struct lws* wsi, const char* accept_header) { void init_pow_config() { log_info("Initializing NIP-13 Proof of Work configuration"); - // Load PoW settings from configuration system - g_pow_config.enabled = get_config_bool("pow_enabled", 1); - g_pow_config.min_pow_difficulty = get_config_int("pow_min_difficulty", 0); - - // Get PoW mode from configuration + // Get all config values first (without holding mutex to avoid deadlock) + int pow_enabled = get_config_bool("pow_enabled", 1); + int pow_min_difficulty = get_config_int("pow_min_difficulty", 0); const char* pow_mode = get_config_value("pow_mode"); + + pthread_mutex_lock(&g_unified_cache.cache_lock); + + // Load PoW settings from configuration system + g_unified_cache.pow_config.enabled = pow_enabled; + g_unified_cache.pow_config.min_pow_difficulty = pow_min_difficulty; + + // Configure PoW mode if (pow_mode) { if (strcmp(pow_mode, "strict") == 0) { - g_pow_config.validation_flags = NOSTR_POW_VALIDATE_ANTI_SPAM | NOSTR_POW_STRICT_FORMAT; - g_pow_config.require_nonce_tag = 1; - g_pow_config.reject_lower_targets = 1; - g_pow_config.strict_format = 1; - g_pow_config.anti_spam_mode = 1; + g_unified_cache.pow_config.validation_flags = NOSTR_POW_VALIDATE_ANTI_SPAM | NOSTR_POW_STRICT_FORMAT; + g_unified_cache.pow_config.require_nonce_tag = 1; + g_unified_cache.pow_config.reject_lower_targets = 1; + g_unified_cache.pow_config.strict_format = 1; + g_unified_cache.pow_config.anti_spam_mode = 1; log_info("PoW configured in strict anti-spam mode"); } else if (strcmp(pow_mode, "full") == 0) { - g_pow_config.validation_flags = NOSTR_POW_VALIDATE_FULL; - g_pow_config.require_nonce_tag = 1; + g_unified_cache.pow_config.validation_flags = NOSTR_POW_VALIDATE_FULL; + g_unified_cache.pow_config.require_nonce_tag = 1; log_info("PoW configured in full validation mode"); } else if (strcmp(pow_mode, "basic") == 0) { - g_pow_config.validation_flags = NOSTR_POW_VALIDATE_BASIC; + g_unified_cache.pow_config.validation_flags = NOSTR_POW_VALIDATE_BASIC; log_info("PoW configured in basic validation mode"); } else if (strcmp(pow_mode, "disabled") == 0) { - g_pow_config.enabled = 0; + g_unified_cache.pow_config.enabled = 0; log_info("PoW validation disabled via configuration"); } } else { // Default to basic mode - g_pow_config.validation_flags = NOSTR_POW_VALIDATE_BASIC; + g_unified_cache.pow_config.validation_flags = NOSTR_POW_VALIDATE_BASIC; log_info("PoW configured in basic validation mode (default)"); } @@ -1900,17 +1931,25 @@ void init_pow_config() { char config_msg[512]; snprintf(config_msg, sizeof(config_msg), "PoW Configuration: enabled=%s, min_difficulty=%d, validation_flags=0x%x, mode=%s", - g_pow_config.enabled ? "true" : "false", - g_pow_config.min_pow_difficulty, - g_pow_config.validation_flags, - g_pow_config.anti_spam_mode ? "anti-spam" : - (g_pow_config.validation_flags & NOSTR_POW_VALIDATE_FULL) ? "full" : "basic"); + g_unified_cache.pow_config.enabled ? "true" : "false", + g_unified_cache.pow_config.min_pow_difficulty, + g_unified_cache.pow_config.validation_flags, + g_unified_cache.pow_config.anti_spam_mode ? "anti-spam" : + (g_unified_cache.pow_config.validation_flags & NOSTR_POW_VALIDATE_FULL) ? "full" : "basic"); log_info(config_msg); + + pthread_mutex_unlock(&g_unified_cache.cache_lock); } // Validate event Proof of Work according to NIP-13 int validate_event_pow(cJSON* event, char* error_message, size_t error_size) { - if (!g_pow_config.enabled) { + pthread_mutex_lock(&g_unified_cache.cache_lock); + int enabled = g_unified_cache.pow_config.enabled; + int min_pow_difficulty = g_unified_cache.pow_config.min_pow_difficulty; + int validation_flags = g_unified_cache.pow_config.validation_flags; + pthread_mutex_unlock(&g_unified_cache.cache_lock); + + if (!enabled) { return 0; // PoW validation disabled } @@ -1921,7 +1960,7 @@ int validate_event_pow(cJSON* event, char* error_message, size_t error_size) { // If min_pow_difficulty is 0, only validate events that have nonce tags // This allows events without PoW when difficulty requirement is 0 - if (g_pow_config.min_pow_difficulty == 0) { + if (min_pow_difficulty == 0) { cJSON* tags = cJSON_GetObjectItem(event, "tags"); int has_nonce_tag = 0; @@ -1949,8 +1988,8 @@ int validate_event_pow(cJSON* event, char* error_message, size_t error_size) { // Perform PoW validation using nostr_core_lib nostr_pow_result_t pow_result; - int validation_result = nostr_validate_pow(event, g_pow_config.min_pow_difficulty, - g_pow_config.validation_flags, &pow_result); + int validation_result = nostr_validate_pow(event, min_pow_difficulty, + validation_flags, &pow_result); if (validation_result != NOSTR_SUCCESS) { // Handle specific error cases with appropriate messages @@ -1958,12 +1997,12 @@ int validate_event_pow(cJSON* event, char* error_message, size_t error_size) { case NOSTR_ERROR_NIP13_INSUFFICIENT: snprintf(error_message, error_size, "pow: insufficient difficulty: %d < %d", - pow_result.actual_difficulty, g_pow_config.min_pow_difficulty); + pow_result.actual_difficulty, min_pow_difficulty); log_warning("Event rejected: insufficient PoW difficulty"); break; case NOSTR_ERROR_NIP13_NO_NONCE_TAG: // This should not happen with min_difficulty=0 after our check above - if (g_pow_config.min_pow_difficulty > 0) { + if (min_pow_difficulty > 0) { snprintf(error_message, error_size, "pow: missing required nonce tag"); log_warning("Event rejected: missing nonce tag"); } else { @@ -1977,7 +2016,7 @@ int validate_event_pow(cJSON* event, char* error_message, size_t error_size) { case NOSTR_ERROR_NIP13_TARGET_MISMATCH: snprintf(error_message, error_size, "pow: committed target (%d) lower than minimum (%d)", - pow_result.committed_target, g_pow_config.min_pow_difficulty); + pow_result.committed_target, min_pow_difficulty); log_warning("Event rejected: committed target too low (anti-spam protection)"); break; case NOSTR_ERROR_NIP13_CALCULATION: @@ -1997,7 +2036,7 @@ int validate_event_pow(cJSON* event, char* error_message, size_t error_size) { } // Log successful PoW validation (only if minimum difficulty is required) - if (g_pow_config.min_pow_difficulty > 0 || pow_result.has_nonce_tag) { + if (min_pow_difficulty > 0 || pow_result.has_nonce_tag) { char debug_msg[256]; snprintf(debug_msg, sizeof(debug_msg), "PoW validated: difficulty=%d, target=%d, nonce=%llu%s", @@ -2021,28 +2060,39 @@ int validate_event_pow(cJSON* event, char* error_message, size_t error_size) { void init_expiration_config() { log_info("Initializing NIP-40 Expiration Timestamp configuration"); + // Get all config values first (without holding mutex to avoid deadlock) + int expiration_enabled = get_config_bool("expiration_enabled", 1); + int expiration_strict = get_config_bool("expiration_strict", 1); + int expiration_filter = get_config_bool("expiration_filter", 1); + int expiration_delete = get_config_bool("expiration_delete", 0); + long expiration_grace_period = get_config_int("expiration_grace_period", 1); + + pthread_mutex_lock(&g_unified_cache.cache_lock); + // Load expiration settings from configuration system - g_expiration_config.enabled = get_config_bool("expiration_enabled", 1); - g_expiration_config.strict_mode = get_config_bool("expiration_strict", 1); - g_expiration_config.filter_responses = get_config_bool("expiration_filter", 1); - g_expiration_config.delete_expired = get_config_bool("expiration_delete", 0); - g_expiration_config.grace_period = get_config_int("expiration_grace_period", 1); + g_unified_cache.expiration_config.enabled = expiration_enabled; + g_unified_cache.expiration_config.strict_mode = expiration_strict; + g_unified_cache.expiration_config.filter_responses = expiration_filter; + g_unified_cache.expiration_config.delete_expired = expiration_delete; + g_unified_cache.expiration_config.grace_period = expiration_grace_period; // Validate grace period bounds - if (g_expiration_config.grace_period < 0 || g_expiration_config.grace_period > 86400) { + if (g_unified_cache.expiration_config.grace_period < 0 || g_unified_cache.expiration_config.grace_period > 86400) { log_warning("Invalid grace period, using default of 300 seconds"); - g_expiration_config.grace_period = 300; + g_unified_cache.expiration_config.grace_period = 300; } // Log final configuration char config_msg[512]; snprintf(config_msg, sizeof(config_msg), "Expiration Configuration: enabled=%s, strict_mode=%s, filter_responses=%s, grace_period=%ld seconds", - g_expiration_config.enabled ? "true" : "false", - g_expiration_config.strict_mode ? "true" : "false", - g_expiration_config.filter_responses ? "true" : "false", - g_expiration_config.grace_period); + g_unified_cache.expiration_config.enabled ? "true" : "false", + g_unified_cache.expiration_config.strict_mode ? "true" : "false", + g_unified_cache.expiration_config.filter_responses ? "true" : "false", + g_unified_cache.expiration_config.grace_period); log_info(config_msg); + + pthread_mutex_unlock(&g_unified_cache.cache_lock); } // Extract expiration timestamp from event tags @@ -2112,12 +2162,22 @@ int is_event_expired(cJSON* event, time_t current_time) { } // Check if current time exceeds expiration + grace period - return (current_time > (expiration_ts + g_expiration_config.grace_period)); + pthread_mutex_lock(&g_unified_cache.cache_lock); + long grace_period = g_unified_cache.expiration_config.grace_period; + pthread_mutex_unlock(&g_unified_cache.cache_lock); + + return (current_time > (expiration_ts + grace_period)); } // Validate event expiration according to NIP-40 int validate_event_expiration(cJSON* event, char* error_message, size_t error_size) { - if (!g_expiration_config.enabled) { + pthread_mutex_lock(&g_unified_cache.cache_lock); + int enabled = g_unified_cache.expiration_config.enabled; + int strict_mode = g_unified_cache.expiration_config.strict_mode; + long grace_period = g_unified_cache.expiration_config.grace_period; + pthread_mutex_unlock(&g_unified_cache.cache_lock); + + if (!enabled) { return 0; // Expiration validation disabled } @@ -2129,13 +2189,13 @@ int validate_event_expiration(cJSON* event, char* error_message, size_t error_si // Check if event is expired time_t current_time = time(NULL); if (is_event_expired(event, current_time)) { - if (g_expiration_config.strict_mode) { + if (strict_mode) { cJSON* tags = cJSON_GetObjectItem(event, "tags"); long expiration_ts = extract_expiration_timestamp(tags); snprintf(error_message, error_size, "invalid: event expired (expiration=%ld, current=%ld, grace=%ld)", - expiration_ts, (long)current_time, g_expiration_config.grace_period); + expiration_ts, (long)current_time, grace_period); log_warning("Event rejected: expired timestamp"); return -1; } else { @@ -2631,6 +2691,54 @@ int handle_req_message(const char* sub_id, cJSON* filters, struct lws *wsi, stru return 0; } + // Check for kind 33334 configuration event requests BEFORE creating subscription + int config_events_sent = 0; + int has_config_request = 0; + + // Check if any filter requests kind 33334 (configuration events) + for (int i = 0; i < cJSON_GetArraySize(filters); i++) { + cJSON* filter = cJSON_GetArrayItem(filters, i); + if (filter && cJSON_IsObject(filter)) { + if (req_filter_requests_config_events(filter)) { + has_config_request = 1; + + // Generate synthetic config event for this subscription + cJSON* filters_array = cJSON_CreateArray(); + cJSON_AddItemToArray(filters_array, cJSON_Duplicate(filter, 1)); + + cJSON* event_msg = generate_synthetic_config_event_for_subscription(sub_id, filters_array); + + if (event_msg) { + char* msg_str = cJSON_Print(event_msg); + if (msg_str) { + size_t msg_len = strlen(msg_str); + unsigned char* buf = malloc(LWS_PRE + msg_len); + if (buf) { + memcpy(buf + LWS_PRE, msg_str, msg_len); + lws_write(wsi, buf + LWS_PRE, msg_len, LWS_WRITE_TEXT); + config_events_sent++; + free(buf); + } + free(msg_str); + } + cJSON_Delete(event_msg); + } + + cJSON_Delete(filters_array); + + char debug_msg[256]; + snprintf(debug_msg, sizeof(debug_msg), + "Generated %d synthetic config events for subscription %s", + config_events_sent, sub_id); + log_info(debug_msg); + break; // Only generate once per subscription + } + } + } + + // If only config events were requested, we can return early after sending EOSE + // But still create the subscription for future config updates + // Check session subscription limits if (pss && pss->subscription_count >= g_subscription_manager.max_subscriptions_per_client) { log_error("Maximum subscriptions per client exceeded"); @@ -2654,14 +2762,14 @@ int handle_req_message(const char* sub_id, cJSON* filters, struct lws *wsi, stru } cJSON_Delete(closed_msg); - return 0; + return has_config_request ? config_events_sent : 0; } // Create persistent subscription subscription_t* subscription = create_subscription(sub_id, wsi, filters, pss ? pss->client_ip : "unknown"); if (!subscription) { log_error("Failed to create subscription"); - return 0; + return has_config_request ? config_events_sent : 0; } // Add to global manager @@ -2688,7 +2796,7 @@ int handle_req_message(const char* sub_id, cJSON* filters, struct lws *wsi, stru } cJSON_Delete(closed_msg); - return 0; + return has_config_request ? config_events_sent : 0; } // Add to session's subscription list (if session data available) @@ -2700,7 +2808,7 @@ int handle_req_message(const char* sub_id, cJSON* filters, struct lws *wsi, stru pthread_mutex_unlock(&pss->session_lock); } - int events_sent = 0; + int events_sent = config_events_sent; // Start with synthetic config events // Process each filter in the array for (int i = 0; i < cJSON_GetArraySize(filters); i++) { @@ -2847,7 +2955,12 @@ int handle_req_message(const char* sub_id, cJSON* filters, struct lws *wsi, stru cJSON_AddItemToObject(event, "tags", tags); // Check expiration filtering (NIP-40) at application level - if (g_expiration_config.enabled && g_expiration_config.filter_responses) { + pthread_mutex_lock(&g_unified_cache.cache_lock); + int expiration_enabled = g_unified_cache.expiration_config.enabled; + int filter_responses = g_unified_cache.expiration_config.filter_responses; + pthread_mutex_unlock(&g_unified_cache.cache_lock); + + if (expiration_enabled && filter_responses) { time_t current_time = time(NULL); if (is_event_expired(event, current_time)) { // Skip this expired event @@ -2958,8 +3071,13 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso // Get real client IP address char client_ip[CLIENT_IP_MAX_LENGTH]; lws_get_peer_simple(wsi, client_ip, sizeof(client_ip)); - strncpy(pss->client_ip, client_ip, CLIENT_IP_MAX_LENGTH - 1); - pss->client_ip[CLIENT_IP_MAX_LENGTH - 1] = '\0'; + + // Ensure client_ip is null-terminated and copy safely + client_ip[CLIENT_IP_MAX_LENGTH - 1] = '\0'; + size_t ip_len = strlen(client_ip); + size_t copy_len = (ip_len < CLIENT_IP_MAX_LENGTH - 1) ? ip_len : CLIENT_IP_MAX_LENGTH - 1; + memcpy(pss->client_ip, client_ip, copy_len); + pss->client_ip[copy_len] = '\0'; // Initialize NIP-42 authentication state pss->authenticated = 0; @@ -3109,7 +3227,10 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso if (process_admin_event_in_config(event, admin_error, sizeof(admin_error)) != 0) { log_error("Failed to process admin event through admin API"); result = -1; - strncpy(error_message, admin_error, sizeof(error_message) - 1); + size_t error_len = strlen(admin_error); + size_t copy_len = (error_len < sizeof(error_message) - 1) ? error_len : sizeof(error_message) - 1; + memcpy(error_message, admin_error, copy_len); + error_message[copy_len] = '\0'; } else { log_success("Admin event processed successfully through admin API"); // Admin events are processed by the admin API, not broadcast to subscriptions @@ -3684,10 +3805,29 @@ int main(int argc, char* argv[]) { return 1; } + // Systematically add pubkeys to config table + if (add_pubkeys_to_config_table() != 0) { + log_warning("Failed to add pubkeys to config table systematically"); + } else { + log_success("Pubkeys added to config table systematically"); + } + // Retry storing the configuration event now that database is initialized if (retry_store_initial_config_event() != 0) { log_warning("Failed to store initial configuration event after database init"); } + + // Now store the pubkeys in config table since database is available + const char* admin_pubkey = get_admin_pubkey_cached(); + const char* relay_pubkey_from_cache = get_relay_pubkey_cached(); + if (admin_pubkey && strlen(admin_pubkey) == 64) { + set_config_value_in_table("admin_pubkey", admin_pubkey, "string", "Administrator public key", "authentication", 0); + log_success("Admin pubkey stored in config table for first-time startup"); + } + if (relay_pubkey_from_cache && strlen(relay_pubkey_from_cache) == 64) { + set_config_value_in_table("relay_pubkey", relay_pubkey_from_cache, "string", "Relay public key", "relay", 0); + log_success("Relay pubkey stored in config table for first-time startup"); + } } else { log_info("Existing relay detected"); @@ -3757,6 +3897,21 @@ int main(int argc, char* argv[]) { log_warning("Failed to apply configuration from database"); } else { log_success("Configuration loaded from database"); + + // Extract admin pubkey from the config event and store in config table for unified cache access + cJSON* pubkey_obj = cJSON_GetObjectItem(config_event, "pubkey"); + const char* admin_pubkey = pubkey_obj ? cJSON_GetStringValue(pubkey_obj) : NULL; + + // Store both admin and relay pubkeys in config table for unified cache + if (admin_pubkey && strlen(admin_pubkey) == 64) { + set_config_value_in_table("admin_pubkey", admin_pubkey, "string", "Administrator public key", "authentication", 0); + log_info("Admin pubkey stored in config table for existing relay"); + } + + if (relay_pubkey && strlen(relay_pubkey) == 64) { + set_config_value_in_table("relay_pubkey", relay_pubkey, "string", "Relay public key", "relay", 0); + log_info("Relay pubkey stored in config table for existing relay"); + } } cJSON_Delete(config_event); } else { diff --git a/src/request_validator.c b/src/request_validator.c index 0b1b0eb..c548d44 100644 --- a/src/request_validator.c +++ b/src/request_validator.c @@ -132,24 +132,11 @@ typedef struct { int time_tolerance_seconds; } nip42_challenge_manager_t; -// Cached configuration structure -typedef struct { - int auth_required; // Whether authentication is required - long max_file_size; // Maximum file size in bytes - int admin_enabled; // Whether admin interface is enabled - char admin_pubkey[65]; // Admin public key - int nip42_mode; // NIP-42 authentication mode - int nip42_challenge_timeout; // NIP-42 challenge timeout in seconds - int nip42_time_tolerance; // NIP-42 time tolerance in seconds - time_t cache_expires; // When cache expires - int cache_valid; // Whether cache is valid -} auth_config_cache_t; - //============================================================================= // GLOBAL STATE //============================================================================= -static auth_config_cache_t g_auth_cache = {0}; +// No longer using local auth cache - using unified cache from config.c static nip42_challenge_manager_t g_challenge_manager = {0}; static int g_validator_initialized = 0; @@ -222,15 +209,15 @@ int ginxsom_request_validator_init(const char *db_path, const char *app_name) { return result; } - // Initialize NIP-42 challenge manager + // Initialize NIP-42 challenge manager using unified config memset(&g_challenge_manager, 0, sizeof(g_challenge_manager)); - g_challenge_manager.timeout_seconds = - g_auth_cache.nip42_challenge_timeout > 0 - ? g_auth_cache.nip42_challenge_timeout - : 600; - g_challenge_manager.time_tolerance_seconds = - g_auth_cache.nip42_time_tolerance > 0 ? g_auth_cache.nip42_time_tolerance - : 300; + + const char* nip42_timeout = get_config_value("nip42_challenge_timeout"); + g_challenge_manager.timeout_seconds = nip42_timeout ? atoi(nip42_timeout) : 600; + + const char* nip42_tolerance = get_config_value("nip42_time_tolerance"); + g_challenge_manager.time_tolerance_seconds = nip42_tolerance ? atoi(nip42_tolerance) : 300; + g_challenge_manager.last_cleanup = time(NULL); g_validator_initialized = 1; @@ -243,12 +230,15 @@ int ginxsom_request_validator_init(const char *db_path, const char *app_name) { * Check if authentication rules are enabled */ int nostr_auth_rules_enabled(void) { - // Reload config if cache expired - if (!g_auth_cache.cache_valid || time(NULL) > g_auth_cache.cache_expires) { - reload_auth_config(); + // Use unified cache from config.c + const char* auth_enabled = get_config_value("auth_enabled"); + if (auth_enabled && strcmp(auth_enabled, "true") == 0) { + return 1; } - - return g_auth_cache.auth_required; + + // Also check legacy key + const char* auth_rules_enabled = get_config_value("auth_rules_enabled"); + return (auth_rules_enabled && strcmp(auth_rules_enabled, "true") == 0) ? 1 : 0; } /////////////////////////////////////////////////////////////////////////////////////// @@ -306,14 +296,12 @@ int nostr_validate_unified_request(const char* json_string, size_t json_length) int event_kind = (int)cJSON_GetNumberValue(kind); - // 5. Reload config if needed - if (!g_auth_cache.cache_valid || time(NULL) > g_auth_cache.cache_expires) { - reload_auth_config(); - } + // 5. Check configuration using unified cache + int auth_required = nostr_auth_rules_enabled(); char config_msg[256]; sprintf(config_msg, "VALIDATOR_DEBUG: STEP 5 PASSED - Event kind: %d, auth_required: %d\n", - event_kind, g_auth_cache.auth_required); + event_kind, auth_required); validator_debug_log(config_msg); ///////////////////////////////////////////////////////////////////// @@ -352,7 +340,9 @@ int nostr_validate_unified_request(const char* json_string, size_t json_length) if (event_kind == 22242) { validator_debug_log("VALIDATOR_DEBUG: STEP 8 - Processing NIP-42 challenge response\n"); - if (g_auth_cache.nip42_mode == 0) { + // Check NIP-42 mode using unified cache + const char* nip42_enabled = get_config_value("nip42_auth_enabled"); + if (nip42_enabled && strcmp(nip42_enabled, "false") == 0) { validator_debug_log("VALIDATOR_DEBUG: STEP 8 FAILED - NIP-42 is disabled\n"); cJSON_Delete(event); return NOSTR_ERROR_NIP42_DISABLED; @@ -370,7 +360,7 @@ int nostr_validate_unified_request(const char* json_string, size_t json_length) ///////////////////////////////////////////////////////////////////// // 9. Check if authentication rules are enabled - if (!g_auth_cache.auth_required) { + if (!auth_required) { validator_debug_log("VALIDATOR_DEBUG: STEP 9 - Authentication disabled, skipping database auth rules\n"); } else { // 10. Check database authentication rules (only if auth enabled) @@ -404,17 +394,23 @@ int nostr_validate_unified_request(const char* json_string, size_t json_length) ///////////////////////////////////////////////////////////////////// // 11. NIP-13 Proof of Work validation - if (g_pow_config.enabled && g_pow_config.min_pow_difficulty > 0) { + pthread_mutex_lock(&g_unified_cache.cache_lock); + int pow_enabled = g_unified_cache.pow_config.enabled; + int pow_min_difficulty = g_unified_cache.pow_config.min_pow_difficulty; + int pow_validation_flags = g_unified_cache.pow_config.validation_flags; + pthread_mutex_unlock(&g_unified_cache.cache_lock); + + if (pow_enabled && pow_min_difficulty > 0) { validator_debug_log("VALIDATOR_DEBUG: STEP 11 - Validating NIP-13 Proof of Work\n"); nostr_pow_result_t pow_result; - int pow_validation_result = nostr_validate_pow(event, g_pow_config.min_pow_difficulty, - g_pow_config.validation_flags, &pow_result); + int pow_validation_result = nostr_validate_pow(event, pow_min_difficulty, + pow_validation_flags, &pow_result); if (pow_validation_result != NOSTR_SUCCESS) { char pow_msg[256]; sprintf(pow_msg, "VALIDATOR_DEBUG: STEP 11 FAILED - PoW validation failed (error=%d, difficulty=%d/%d)\n", - pow_validation_result, pow_result.actual_difficulty, g_pow_config.min_pow_difficulty); + pow_validation_result, pow_result.actual_difficulty, pow_min_difficulty); validator_debug_log(pow_msg); cJSON_Delete(event); return pow_validation_result; @@ -553,7 +549,6 @@ void nostr_request_validator_clear_violation(void) { */ void ginxsom_request_validator_cleanup(void) { g_validator_initialized = 0; - memset(&g_auth_cache, 0, sizeof(g_auth_cache)); nostr_request_validator_clear_violation(); } @@ -573,145 +568,22 @@ void nostr_request_result_free_file_data(nostr_request_result_t *result) { // HELPER FUNCTIONS //============================================================================= -/** - * Get cache timeout from environment variable or default - */ -static int get_cache_timeout(void) { - char *no_cache = getenv("GINX_NO_CACHE"); - char *cache_timeout = getenv("GINX_CACHE_TIMEOUT"); - - if (no_cache && strcmp(no_cache, "1") == 0) { - return 0; // No caching - } - - if (cache_timeout) { - int timeout = atoi(cache_timeout); - return (timeout >= 0) ? timeout : 300; // Use provided value or default - } - - return 300; // Default 5 minutes -} /** - * Force cache refresh - invalidates current cache + * Force cache refresh - use unified cache system */ void nostr_request_validator_force_cache_refresh(void) { - g_auth_cache.cache_valid = 0; - g_auth_cache.cache_expires = 0; - validator_debug_log("VALIDATOR: Cache forcibly invalidated\n"); + // Use unified cache refresh from config.c + force_config_cache_refresh(); + validator_debug_log("VALIDATOR: Cache forcibly invalidated via unified cache\n"); } /** - * Reload authentication configuration from unified config table + * This function is no longer needed - configuration is handled by unified cache */ static int reload_auth_config(void) { - sqlite3 *db = NULL; - sqlite3_stmt *stmt = NULL; - int rc; - - // Clear cache - memset(&g_auth_cache, 0, sizeof(g_auth_cache)); - - // Open database using global database path - if (strlen(g_database_path) == 0) { - validator_debug_log("VALIDATOR: No database path available\n"); - // Use defaults - g_auth_cache.auth_required = 0; - g_auth_cache.max_file_size = 104857600; // 100MB - g_auth_cache.admin_enabled = 0; - g_auth_cache.nip42_mode = 1; // Optional - int cache_timeout = get_cache_timeout(); - g_auth_cache.cache_expires = time(NULL) + cache_timeout; - g_auth_cache.cache_valid = 1; - return NOSTR_SUCCESS; - } - - rc = sqlite3_open_v2(g_database_path, &db, SQLITE_OPEN_READONLY, NULL); - if (rc != SQLITE_OK) { - validator_debug_log("VALIDATOR: Could not open database\n"); - // Use defaults - g_auth_cache.auth_required = 0; - g_auth_cache.max_file_size = 104857600; // 100MB - g_auth_cache.admin_enabled = 0; - g_auth_cache.nip42_mode = 1; // Optional - int cache_timeout = get_cache_timeout(); - g_auth_cache.cache_expires = time(NULL) + cache_timeout; - g_auth_cache.cache_valid = 1; - return NOSTR_SUCCESS; - } - - // Load configuration values from unified config table - const char *config_sql = - "SELECT key, value FROM config WHERE key IN ('require_auth', " - "'auth_rules_enabled', 'max_file_size', 'admin_enabled', 'admin_pubkey', " - "'nip42_require_auth', 'nip42_challenge_timeout', " - "'nip42_time_tolerance')"; - rc = sqlite3_prepare_v2(db, config_sql, -1, &stmt, NULL); - - if (rc == SQLITE_OK) { - while (sqlite3_step(stmt) == SQLITE_ROW) { - const char *key = (const char *)sqlite3_column_text(stmt, 0); - const char *value = (const char *)sqlite3_column_text(stmt, 1); - - if (!key || !value) - continue; - - if (strcmp(key, "require_auth") == 0) { - g_auth_cache.auth_required = (strcmp(value, "true") == 0) ? 1 : 0; - } else if (strcmp(key, "auth_rules_enabled") == 0) { - // Override auth_required with auth_rules_enabled if present (higher - // priority) - g_auth_cache.auth_required = (strcmp(value, "true") == 0) ? 1 : 0; - } else if (strcmp(key, "max_file_size") == 0) { - g_auth_cache.max_file_size = atol(value); - } else if (strcmp(key, "admin_enabled") == 0) { - g_auth_cache.admin_enabled = (strcmp(value, "true") == 0) ? 1 : 0; - } else if (strcmp(key, "admin_pubkey") == 0) { - strncpy(g_auth_cache.admin_pubkey, value, - sizeof(g_auth_cache.admin_pubkey) - 1); - } else if (strcmp(key, "nip42_require_auth") == 0) { - if (strcmp(value, "false") == 0) { - g_auth_cache.nip42_mode = 0; // Disabled - } else if (strcmp(value, "required") == 0) { - g_auth_cache.nip42_mode = 2; // Required - } else if (strcmp(value, "true") == 0) { - g_auth_cache.nip42_mode = 1; // Optional/Enabled - } else { - g_auth_cache.nip42_mode = 1; // Default to Optional/Enabled - } - } else if (strcmp(key, "nip42_challenge_timeout") == 0) { - g_auth_cache.nip42_challenge_timeout = atoi(value); - } else if (strcmp(key, "nip42_time_tolerance") == 0) { - g_auth_cache.nip42_time_tolerance = atoi(value); - } - } - sqlite3_finalize(stmt); - } - - sqlite3_close(db); - - // Set cache expiration with environment variable support - int cache_timeout = get_cache_timeout(); - g_auth_cache.cache_expires = time(NULL) + cache_timeout; - g_auth_cache.cache_valid = 1; - - // Set defaults for missing values - if (g_auth_cache.max_file_size == 0) { - g_auth_cache.max_file_size = 104857600; // 100MB - } - - // Debug logging - fprintf(stderr, - "VALIDATOR: Configuration loaded from unified config table - " - "auth_required: %d, max_file_size: %ld, nip42_mode: %d, " - "cache_timeout: %d\n", - g_auth_cache.auth_required, g_auth_cache.max_file_size, - g_auth_cache.nip42_mode, cache_timeout); - fprintf(stderr, - "VALIDATOR: NIP-42 mode details - nip42_mode=%d (0=disabled, " - "1=optional/enabled, 2=required)\n", - g_auth_cache.nip42_mode); - + // Configuration is now handled by the unified cache in config.c + validator_debug_log("VALIDATOR: Using unified cache system for configuration\n"); return NOSTR_SUCCESS; }