{"id":"b2f62cea26d804cf252f4b9b3c9fe5bba929ed5558055e2d55ba90a01f893c97","pubkey":"13837e264f54ddb2a48fb8ce7f17fead89eb00547fc4773499e78a8877c21473","created_at":1759754434,"kind":30817,"tags":[["d","NIP-XMR"],["title","Encrypted Monero Payment Information"],["client","nostrhub.io"]],"content":"# NIP-XMR\n\n## Encrypted Monero Payment Information\n\n`draft` `optional`\n\nThis NIP defines a way for users to store their **view-only** Monero wallet information on Nostr using NIP-44 encryption, enabling portable wallet credentials across multiple services while maintaining privacy.\n\n**⚠️ Important: This NIP ONLY handles view-only wallet data (primary address + private view key). Spend keys are NEVER shared. Services cannot spend your funds, only monitor incoming payments.**\n\n## Motivation\n\nCurrently, users must manually enter their Monero wallet details on every service that accepts Monero. This creates friction and reduces interoperability. Additionally, storing wallet information publicly (like Lightning addresses in Kind 0 profiles) compromises privacy for users who want to receive Monero payments.\n\nThis NIP enables:\n\n- **Portable wallet credentials**: Store wallet details once, use across multiple NIP-XMR compatible services\n- **Privacy-first design**: Wallet details are encrypted and only decryptable by the owner\n- **Service authorization**: Users explicitly authorize services to access their wallet data via Nostr extension\n- **Improved UX**: No need to re-enter wallet details on every platform\n- **Decentralized storage**: No central database holds sensitive wallet information\n\n## Event Format\n\nA NIP-XMR event is a **parameterized replaceable event** (kind `38383`) with NIP-44 encrypted content:\n\n```json\n{\n  \"kind\": 38383,\n  \"created_at\": <unix timestamp>,\n  \"tags\": [\n    [\"p\", \"<user's own pubkey>\"],\n    [\"d\", \"monero-wallet\"]\n  ],\n  \"content\": \"<nip44-encrypted wallet data>\",\n  ...other fields\n}\n```\n\n### Tags\n\n- `p` (required): The user's own pubkey. This indicates the content is encrypted to self.\n- `d` (required): The parameterized replaceable event identifier, always set to `\"monero-wallet\"`. This allows updating wallet information by publishing a new event with the same `d` tag.\n\n### Content\n\nThe `content` field contains **NIP-44 encrypted** JSON data with the following structure (after decryption):\n\n```json\n{\n  \"xmr_address\": \"4AdUndX...\",\n  \"xmr_view_key\": \"f359631...\",\n  \"updated_at\": 1680000000\n}\n```\n\n**Example (for illustration only, not real credentials):**\n```json\n{\n  \"xmr_address\": \"4AdUndXHHZ6cfufTMvppY6JwXNouMBzSkbLYfpAV5Usx3skxNgYeYTRj5UzqtReoS44qo9mtmXCqY45DJ852K5Jv2684Rge\",\n  \"xmr_view_key\": \"f359631075708155cc3d92a32b75a7d02a5dcf27756707b47a2b31b21c389501\",\n  \"updated_at\": 1680000000\n}\n```\n\n**Fields:**\n- `xmr_address` (required): The user's primary Monero address (starts with `4`)\n- `xmr_view_key` (required): The private view key for payment verification\n- `updated_at` (required): Unix timestamp of when wallet info was last updated\n\n## Publishing Workflow\n\n1. User creates/updates their Monero wallet information in a NIP-XMR compatible service (e.g., Gyges)\n2. Service requests user's public key via `window.nostr.getPublicKey()`\n3. Service creates JSON payload with wallet details\n4. Service encrypts payload to user's own pubkey using `window.nostr.nip44.encrypt(pubkey, json)`\n5. Service creates kind `38383` event with encrypted content\n6. User signs event via `window.nostr.signEvent(event)`\n7. Event is published to Nostr relays\n\n## Fetching Workflow\n\n1. User logs into a different NIP-XMR compatible service\n2. Service queries relays for kind `38383` events from user's pubkey with `d` tag `\"monero-wallet\"`\n3. Service requests decryption via `window.nostr.nip44.decrypt(pubkey, encryptedContent)`\n4. User's Nostr extension prompts for permission to decrypt\n5. Upon approval, service receives wallet details and can auto-fill payment information\n\n## Privacy Considerations\n\n### Encryption to Self\n\nBy encrypting to the user's own public key, the wallet data is:\n- **Private**: Only decryptable by the user's private key\n- **Portable**: Accessible from any device where the user has their Nostr private key\n- **Permissioned**: Services must request decryption through the Nostr extension, giving users control\n\n### View Key Security\n\nWhile view keys allow monitoring incoming transactions without spending ability:\n- **View keys reveal incoming payments**: Anyone with the view key can see all payments received\n- **View keys cannot spend funds**: The spend key remains secure\n- **Subaddress privacy**: Services should derive unique subaddresses for each payment to maintain sender privacy\n\nUsers should understand that:\n1. Encrypted data is only as secure as their Nostr private key\n2. View keys allow payment monitoring but not spending\n3. Services can derive subaddresses from the primary address and view key\n\n## Client Implementation\n\n### Publishing Example (JavaScript)\n\n```javascript\nimport { nip44 } from 'nostr-tools';\n\nasync function publishWallet(xmrAddress, xmrViewKey) {\n  const pubkey = await window.nostr.getPublicKey();\n\n  const walletData = JSON.stringify({\n    xmr_address: xmrAddress,\n    xmr_view_key: xmrViewKey,\n    updated_at: Math.floor(Date.now() / 1000)\n  });\n\n  const encryptedContent = await window.nostr.nip44.encrypt(pubkey, walletData);\n\n  const event = {\n    kind: 38383,\n    created_at: Math.floor(Date.now() / 1000),\n    tags: [\n      ['p', pubkey],\n      ['d', 'monero-wallet']\n    ],\n    content: encryptedContent\n  };\n\n  const signedEvent = await window.nostr.signEvent(event);\n  // Publish to relays...\n}\n```\n\n### Fetching Example (JavaScript)\n\n```javascript\nasync function fetchWallet(pubkey) {\n  // Query relays for NIP-XMR event\n  const events = await pool.querySync(relays, {\n    kinds: [38383],\n    authors: [pubkey],\n    '#d': ['monero-wallet'],\n    limit: 1\n  });\n\n  if (events.length === 0) return null;\n\n  // Request decryption from user's extension\n  const decrypted = await window.nostr.nip44.decrypt(pubkey, events[0].content);\n  const walletData = JSON.parse(decrypted);\n\n  return walletData; // { xmr_address, xmr_view_key, updated_at }\n}\n```\n\n## Service Integration\n\n### Recommended Implementation Flow\n\nServices implementing NIP-XMR should follow this pattern:\n\n1. **Check for existing wallet on login:**\n   - Query relays for user's NIP-XMR event (kind `38383`)\n   - If found, offer to \"Import wallet from Nostr\"\n\n2. **Request one-time authorization:**\n   - Ask user's Nostr extension to decrypt the event\n   - User sees permission prompt: \"Allow [Service] to access your Monero wallet info?\"\n   - User approves once\n\n3. **Store in service database:**\n   - Save decrypted wallet data to your database\n   - Encrypt at database level (e.g., using column encryption or full-disk encryption)\n   - This enables invoice generation when user is offline\n\n4. **Runtime operations (no user interaction needed):**\n   - Generate subaddresses for invoices\n   - Monitor payments using view key\n   - Verify transactions automatically\n\n**Important:** NIP-XMR is for **initial onboarding and portability**, not real-time decryption. Services must store wallet credentials to function properly (e.g., generating invoices when creators are offline).\n\n### Subaddress Derivation\n\nServices should use subaddress derivation to:\n- Generate unique addresses for each payment/invoice\n- Maintain payment privacy for users\n- Track payments without address reuse\n\nExample using `monero-ts`:\n```javascript\nimport { MoneroWalletKeys } from 'monero-ts';\n\nasync function generateSubaddress(primaryAddress, viewKey, accountIdx, subaddressIdx) {\n  const wallet = await MoneroWalletKeys.createWallet({\n    networkType: 'mainnet',\n    primaryAddress: primaryAddress,\n    privateViewKey: viewKey\n  });\n\n  const subaddress = await wallet.getSubaddress(accountIdx, subaddressIdx);\n  return subaddress.getAddress();\n}\n```\n\n## Security Considerations\n\n### Trust Model\n\n⚠️ **Important: NIP-XMR is a wallet credential portability standard, not a runtime decryption system.**\n\nWhen you authorize a service to decrypt your NIP-XMR data, you are trusting that service with your wallet credentials. Services are **expected to store wallet data in their own databases** after authorization, as real-time decryption is not practical for operations like invoice generation (which happen when you're offline).\n\n**What you're sharing:**\n- ✅ **Monero primary address** (public, safe to share)\n- ✅ **Private view key** (allows monitoring incoming payments only)\n- ❌ **NOT your spend key** (NIP-XMR never involves spend keys)\n\n**What services can do with this data:**\n- ✅ Generate unique subaddresses for payments\n- ✅ Monitor incoming payments to verify transactions\n- ❌ **CANNOT spend your funds** (no spend key is shared)\n\n**Risk model:**\n- A malicious service could leak your view key, exposing your payment history\n- A malicious service **CANNOT steal your funds** (view-only access)\n- Only grant access to services you trust\n- Consider using separate Monero wallets for different trust levels\n\n### User Responsibilities\n\n- **Protect Nostr private key**: Compromise of the Nostr private key exposes encrypted wallet data\n- **Use hardware/secure extensions**: Store Nostr keys in hardware devices or secure extensions when possible\n- **Separate wallets for different trust levels**: Use a dedicated wallet for public/service payments vs private holdings\n- **Understand view key privacy**: View keys reveal all incoming transactions but cannot spend funds\n- **Only authorize trusted services**: Once decrypted, services can store your wallet data\n\n### Service Responsibilities\n\n- **Store wallet data securely**: Encrypt wallet data at rest in your database (e.g., using database-level encryption)\n- **Validate addresses**: Verify Monero addresses are valid before using them\n- **Clear permission prompts**: Inform users why wallet data access is needed and what it allows\n- **Secure subaddress generation**: Use battle-tested libraries like `monero-ts` for cryptographic operations\n- **Never request spend keys**: NIP-XMR only uses view keys; never ask users for spend keys\n- **Be transparent**: Clearly document that you store wallet credentials after authorization\n\n### Extension Requirements\n\nNIP-XMR requires Nostr extensions to support:\n- **NIP-44 encryption**: `window.nostr.nip44.encrypt(pubkey, plaintext)`\n- **NIP-44 decryption**: `window.nostr.nip44.decrypt(pubkey, ciphertext)`\n- **Permission prompts**: Clear user consent before decrypting sensitive data\n\n## Comparison to Lightning Addresses\n\n| Aspect | Lightning (NIP-05/lud16) | Monero (NIP-XMR) |\n|--------|--------------------------|------------------|\n| Privacy | Public in profile | Encrypted, private |\n| Portability | Hardcoded per profile | Portable across services |\n| Permission | No permission needed | User grants access via extension |\n| Transaction Privacy | Network-level analysis possible | Cryptographically private |\n\n\n\n## Rationale\n\n### Why Kind 38383?\n\nKind `38383` was chosen as a high number to avoid conflicts with existing NIPs while remaining in the parameterized replaceable event range (30000-40000).\n\n### Why NIP-44 Instead of NIP-04?\n\nNIP-04 has known security vulnerabilities and can potentially leak private keys. NIP-44 was professionally audited and uses secure encryption primitives (HKDF, ChaCha20).\n\n### Why Encrypt to Self?\n\nEncrypting to the user's own pubkey provides:\n- Complete privacy (no public wallet exposure)\n- User control (explicit permission via extension)\n- Portability (accessible from any device with user's key)\n- Future compatibility (can be extended for service-specific grants)\n\n## Future Extensions\n\nPotential improvements to this NIP:\n\n1. **Multiple wallets**: Support for multiple Monero wallets with different `d` tag identifiers\n2. **Service-specific grants**: Ability to grant permanent access to specific service pubkeys\n3. **Metadata**: Additional fields like wallet labels, preferred subaddress indices\n4. **Integration with NIP-57**: Hybrid Lightning/Monero payment support\n5. **Recovery hints**: Encrypted seed phrase backup mechanisms\n\n## Changelog\n\n- **2025-01-05**: Initial draft with NIP-44 encryption design","sig":"2a4c60669c8e232752643a228afb782f58d7a8a2d19667e8da1e7cf1b8501fa7d59b882ebd5a4f18c32c2be1a42481237f99524a043316e3010f867242c8e094"}