30817:NIP-XMR

Encrypted Monero Payment Information

boseph

published
2025-10-06

NIP-XMR

Encrypted Monero Payment Information

draft optional

This 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.

⚠️ 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.

Motivation

Currently, 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.

This NIP enables:

  • Portable wallet credentials: Store wallet details once, use across multiple NIP-XMR compatible services
  • Privacy-first design: Wallet details are encrypted and only decryptable by the owner
  • Service authorization: Users explicitly authorize services to access their wallet data via Nostr extension
  • Improved UX: No need to re-enter wallet details on every platform
  • Decentralized storage: No central database holds sensitive wallet information

Event Format

A NIP-XMR event is a parameterized replaceable event (kind 38383) with NIP-44 encrypted content:

{
  "kind": 38383,
  "created_at": <unix timestamp>,
  "tags": [
    ["p", "<user's own pubkey>"],
    ["d", "monero-wallet"]
  ],
  "content": "<nip44-encrypted wallet data>",
  ...other fields
}

Tags

  • p (required): The user's own pubkey. This indicates the content is encrypted to self.
  • 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.

Content

The content field contains NIP-44 encrypted JSON data with the following structure (after decryption):

{
  "xmr_address": "4AdUndX...",
  "xmr_view_key": "f359631...",
  "updated_at": 1680000000
}

Example (for illustration only, not real credentials):

{
  "xmr_address": "4AdUndXHHZ6cfufTMvppY6JwXNouMBzSkbLYfpAV5Usx3skxNgYeYTRj5UzqtReoS44qo9mtmXCqY45DJ852K5Jv2684Rge",
  "xmr_view_key": "f359631075708155cc3d92a32b75a7d02a5dcf27756707b47a2b31b21c389501",
  "updated_at": 1680000000
}

Fields:

  • xmr_address (required): The user's primary Monero address (starts with 4)
  • xmr_view_key (required): The private view key for payment verification
  • updated_at (required): Unix timestamp of when wallet info was last updated

Publishing Workflow

  1. User creates/updates their Monero wallet information in a NIP-XMR compatible service (e.g., Gyges)
  2. Service requests user's public key via window.nostr.getPublicKey()
  3. Service creates JSON payload with wallet details
  4. Service encrypts payload to user's own pubkey using window.nostr.nip44.encrypt(pubkey, json)
  5. Service creates kind 38383 event with encrypted content
  6. User signs event via window.nostr.signEvent(event)
  7. Event is published to Nostr relays

Fetching Workflow

  1. User logs into a different NIP-XMR compatible service
  2. Service queries relays for kind 38383 events from user's pubkey with d tag "monero-wallet"
  3. Service requests decryption via window.nostr.nip44.decrypt(pubkey, encryptedContent)
  4. User's Nostr extension prompts for permission to decrypt
  5. Upon approval, service receives wallet details and can auto-fill payment information

Privacy Considerations

Encryption to Self

By encrypting to the user's own public key, the wallet data is:

  • Private: Only decryptable by the user's private key
  • Portable: Accessible from any device where the user has their Nostr private key
  • Permissioned: Services must request decryption through the Nostr extension, giving users control

View Key Security

While view keys allow monitoring incoming transactions without spending ability:

  • View keys reveal incoming payments: Anyone with the view key can see all payments received
  • View keys cannot spend funds: The spend key remains secure
  • Subaddress privacy: Services should derive unique subaddresses for each payment to maintain sender privacy

Users should understand that:

  1. Encrypted data is only as secure as their Nostr private key
  2. View keys allow payment monitoring but not spending
  3. Services can derive subaddresses from the primary address and view key

Client Implementation

Publishing Example (JavaScript)

import { nip44 } from 'nostr-tools';

async function publishWallet(xmrAddress, xmrViewKey) {
  const pubkey = await window.nostr.getPublicKey();

  const walletData = JSON.stringify({
    xmr_address: xmrAddress,
    xmr_view_key: xmrViewKey,
    updated_at: Math.floor(Date.now() / 1000)
  });

  const encryptedContent = await window.nostr.nip44.encrypt(pubkey, walletData);

  const event = {
    kind: 38383,
    created_at: Math.floor(Date.now() / 1000),
    tags: [
      ['p', pubkey],
      ['d', 'monero-wallet']
    ],
    content: encryptedContent
  };

  const signedEvent = await window.nostr.signEvent(event);
  // Publish to relays...
}

Fetching Example (JavaScript)

async function fetchWallet(pubkey) {
  // Query relays for NIP-XMR event
  const events = await pool.querySync(relays, {
    kinds: [38383],
    authors: [pubkey],
    '#d': ['monero-wallet'],
    limit: 1
  });

  if (events.length === 0) return null;

  // Request decryption from user's extension
  const decrypted = await window.nostr.nip44.decrypt(pubkey, events[0].content);
  const walletData = JSON.parse(decrypted);

  return walletData; // { xmr_address, xmr_view_key, updated_at }
}

Service Integration

Services implementing NIP-XMR should follow this pattern:

  1. Check for existing wallet on login:

    • Query relays for user's NIP-XMR event (kind 38383)
    • If found, offer to "Import wallet from Nostr"
  2. Request one-time authorization:

    • Ask user's Nostr extension to decrypt the event
    • User sees permission prompt: "Allow [Service] to access your Monero wallet info?"
    • User approves once
  3. Store in service database:

    • Save decrypted wallet data to your database
    • Encrypt at database level (e.g., using column encryption or full-disk encryption)
    • This enables invoice generation when user is offline
  4. Runtime operations (no user interaction needed):

    • Generate subaddresses for invoices
    • Monitor payments using view key
    • Verify transactions automatically

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).

Subaddress Derivation

Services should use subaddress derivation to:

  • Generate unique addresses for each payment/invoice
  • Maintain payment privacy for users
  • Track payments without address reuse

Example using monero-ts:

import { MoneroWalletKeys } from 'monero-ts';

async function generateSubaddress(primaryAddress, viewKey, accountIdx, subaddressIdx) {
  const wallet = await MoneroWalletKeys.createWallet({
    networkType: 'mainnet',
    primaryAddress: primaryAddress,
    privateViewKey: viewKey
  });

  const subaddress = await wallet.getSubaddress(accountIdx, subaddressIdx);
  return subaddress.getAddress();
}

Security Considerations

Trust Model

⚠️ Important: NIP-XMR is a wallet credential portability standard, not a runtime decryption system.

When 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).

What you're sharing:

  • Monero primary address (public, safe to share)
  • Private view key (allows monitoring incoming payments only)
  • NOT your spend key (NIP-XMR never involves spend keys)

What services can do with this data:

  • ✅ Generate unique subaddresses for payments
  • ✅ Monitor incoming payments to verify transactions
  • CANNOT spend your funds (no spend key is shared)

Risk model:

  • A malicious service could leak your view key, exposing your payment history
  • A malicious service CANNOT steal your funds (view-only access)
  • Only grant access to services you trust
  • Consider using separate Monero wallets for different trust levels

User Responsibilities

  • Protect Nostr private key: Compromise of the Nostr private key exposes encrypted wallet data
  • Use hardware/secure extensions: Store Nostr keys in hardware devices or secure extensions when possible
  • Separate wallets for different trust levels: Use a dedicated wallet for public/service payments vs private holdings
  • Understand view key privacy: View keys reveal all incoming transactions but cannot spend funds
  • Only authorize trusted services: Once decrypted, services can store your wallet data

Service Responsibilities

  • Store wallet data securely: Encrypt wallet data at rest in your database (e.g., using database-level encryption)
  • Validate addresses: Verify Monero addresses are valid before using them
  • Clear permission prompts: Inform users why wallet data access is needed and what it allows
  • Secure subaddress generation: Use battle-tested libraries like monero-ts for cryptographic operations
  • Never request spend keys: NIP-XMR only uses view keys; never ask users for spend keys
  • Be transparent: Clearly document that you store wallet credentials after authorization

Extension Requirements

NIP-XMR requires Nostr extensions to support:

  • NIP-44 encryption: window.nostr.nip44.encrypt(pubkey, plaintext)
  • NIP-44 decryption: window.nostr.nip44.decrypt(pubkey, ciphertext)
  • Permission prompts: Clear user consent before decrypting sensitive data

Comparison to Lightning Addresses

Aspect Lightning (NIP-05/lud16) Monero (NIP-XMR)
Privacy Public in profile Encrypted, private
Portability Hardcoded per profile Portable across services
Permission No permission needed User grants access via extension
Transaction Privacy Network-level analysis possible Cryptographically private

Rationale

Why Kind 38383?

Kind 38383 was chosen as a high number to avoid conflicts with existing NIPs while remaining in the parameterized replaceable event range (30000-40000).

Why NIP-44 Instead of NIP-04?

NIP-04 has known security vulnerabilities and can potentially leak private keys. NIP-44 was professionally audited and uses secure encryption primitives (HKDF, ChaCha20).

Why Encrypt to Self?

Encrypting to the user's own pubkey provides:

  • Complete privacy (no public wallet exposure)
  • User control (explicit permission via extension)
  • Portability (accessible from any device with user's key)
  • Future compatibility (can be extended for service-specific grants)

Future Extensions

Potential improvements to this NIP:

  1. Multiple wallets: Support for multiple Monero wallets with different d tag identifiers
  2. Service-specific grants: Ability to grant permanent access to specific service pubkeys
  3. Metadata: Additional fields like wallet labels, preferred subaddress indices
  4. Integration with NIP-57: Hybrid Lightning/Monero payment support
  5. Recovery hints: Encrypted seed phrase backup mechanisms

Changelog

  • 2025-01-05: Initial draft with NIP-44 encryption design

Discussion

Connect a key to comment.