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;
}