Security

A signed event can still contain malicious HTML or an unsafe URL. Keep signature checks enabled, but also consider what happens when your app stores, displays or follows the data it receives.

Generate a secure key

After initializing your platform adapter, use Nostr4J's key generators. They use the platform's own CSPRNG.

To generate a private key and derive its public key:

NostrPrivateKey privateKey = NostrPrivateKey.generate();
NostrPublicKey publicKey = privateKey.getPublicKey();

If you need both as a key pair, use the no-argument constructor for a fresh private key from the same secure generator:

NostrKeyPair keys = new NostrKeyPair();

To create a local signer with a new identity in one step:

NostrKeyPairSigner signer = NostrKeyPairSigner.generate();

Call the generator again when you need another random identity. For an account that must survive restarts, save the key with encrypted key storage and load it on subsequent launches.

Let the library generate the key

These generators are secure and hardened by default

Keys and credentials

Keep private keys behind a signer where possible. In a browser, route signing through an extension so your page never sees the key. On other platforms, use a remote signer the same way. Request only the permissions your app needs.

For a local key, use encrypted storage and close the private key or key pair when you're finished with it. Closing a NostrKeyPairSigner does not destroy its key pair. Closing a key cannot erase copies you've made as Java strings. Avoid those copies where possible, and keep private keys, passphrases, NWC URIs and bunker secrets out of logs.

Event content and URLs

Render event content as text, such as with textContent in a browser. If your app supports rich content, sanitize it before inserting HTML. Check URLs before loading profile images, media or links; even an ordinary remote image can reveal a reader's IP address to its host.

Event signature verification does not fetch image URLs from the content. Your application decides whether to load them. Blossom verifies upload descriptors and full downloads by default; keep those checks enabled.

You can check incoming events against your filters locally with subscription.setVerifyMatchLocally(true). Also set limits on how many events you retain, how long requests can wait and how much data a cache can hold.

Use wss:// for relays and https:// for media and LNURL endpoints. Loopback destinations are blocked by default. If you enable them for local development, keep that setting limited to the development process and avoid accepting arbitrary destination URLs from users.

Wallet connections

Anyone holding an NWC URI can use the permissions granted to it. Ask for a connection with only the operations your app needs, store it as a credential and let the user revoke it when they disconnect.

To validate a zap receipt, call Nip57.parseAndValidateZapReceipt(...) against the invoice and request you created. Supply a trusted provider public key directly or through the expected invoice; validation fails without it. A kind-9735 event that parses successfully does not prove the expected payment happened.

RTC Room access

Create a dedicated random key pair for the room:

NostrKeyPair roomKeyPair = new NostrKeyPair();

Generate it once when creating the room, then distribute the shared room key through an authenticated, encrypted channel to the members allowed to join. Each participant uses that same room key pair and a separate identity signer. Keep the room key separate from personal account keys.

A guessable room seed allows others to derive the key and join; use the random key above for private rooms.

Signaling checks event signatures, room authorization and matching application, protocol and session identifiers before processing offers, answers or routes. Current stored presence helps discover participants, while expired and excessively future-dated presence is discarded. These checks do not make a public room private.

RTC channels bound incomplete fragment reassembly: at most 64 pending packets, 1024 fragments per packet and 16 MiB of retained payload per channel. Incomplete state expires after 30 seconds even if no more traffic arrives. Outgoing logical packets are also capped at 16 MiB; stream transfers split data into smaller writes.

When hosting TURN, keep the endpoint behind TLS and retain the server's authentication, frame and queue limits. Follow the deployment instructions to set up the container and reverse proxy.

Provisional signaling is bounded separately from physical neighbors. If your app needs different per-identity admission limits, see RTC connection settings; those quotas do not replace authorization to join a room.

Optional storage and relay features

Enable relay authentication only for URLs trusted to request that identity. Validate relay hints from external data before allowing a connection. Caches authenticate events but do not encrypt their backing storage or prove network completeness. Keep private-event retention and local storage permissions under your application's control.

Use temporary identities and limited wallet connections when trying demos. You don't need to enter a real private key to use the examples on this site.