Skip to content

Signers

All events on the Nostr protocol are signed through a keypair (described in NIP-01).

In NDK this is taken care of by the NDKSigner interface that can be passed in during initialization or later during runtime.

Signing Methods

Before you can sign events you need a signer set-up. There are different ways to sign events and this space is still evolving.

Browser Extensions

A common way to use NDK is to use a browser extension which is described in NIP-07. This mechanism allows the user to sign events with a browser extension to not share their private key with the application.

The most used browser extensions are Nos2x and Alby.

ts
import NDK, { NDKEvent, NDKNip07Signer } from "@nostr-dev-kit/ndk";

const nip07signer = new NDKNip07Signer();
const ndk = new NDK({ signer: nip07signer });

const event = new NDKEvent(ndk);
event.kind = 1;
event.content = "Hello world";
await event.sign();

Anytime you call sign() or publish() on an NDK Event the browser extension will prompt the user to sign the event.

Private Key Signer

NDK provides NDKPrivateKeySigner for managing in-memory private keys. This is useful for development, testing, or applications that manage keys locally.

WARNING

We strongly recommend not using this in production. Requiring users to share their private key is a security risk and should be avoided in favor of using a browser extension or a remote signer.

The private key signer takes the private key in the nsec format.

ts
import NDK, { NDKEvent, NDKPrivateKeySigner } from "@nostr-dev-kit/ndk";

const privateKeySigner = NDKPrivateKeySigner.generate();
const ndk = new NDK({ signer: privateKeySigner });

const event = new NDKEvent(ndk);
event.kind = 1;
event.content = "Hello world";
await event.sign();

This library can also help with generating new keys.

Remote Signer

A Nostr remote signer (aka bunker) is an application or device that securely stores your private key and signs Nostr events on your behalf, preventing you from having to expose the private key. It works by establishing a secure connection (over Nostr relays), as described in NIP-46, where the bunker implementation can approve or deny requests.

To add remote signing support to your application, there are a few things you need:

  • a bunker:// connection string provided by the user
  • A local (client) keypair used to communicate with remote-signer. Can be generated by NDK

Create a NDKNip46Signer with the bunker connection string and local keypair.

ts
// provided by the user
import NDK, { NDKNip46Signer, NDKPrivateKeySigner } from "@nostr-dev-kit/ndk";

const signerConnectionString = "bunker://....";
const ndk = new NDK();

// local keypair generated when signer if first initialised
const clientKeypair = NDKPrivateKeySigner.generate(); //
const clientNsec = clientKeypair.nsec;

// initiate NIP-46 signer
const signer = NDKNip46Signer.bunker(ndk, signerConnectionString, clientNsec);

// promise will resolve once the `kind:24133` event is received
const user = await signer.blockUntilReady();

console.log("Welcome", user.npub);

Sign Events

Once the signer is initialized, you can use it to sign and publish events:

ts
import NDK, { NDKEvent, NDKNip07Signer } from "@nostr-dev-kit/ndk";

const nip07signer = new NDKNip07Signer();
const ndk = new NDK({ signer: nip07signer });

const event = new NDKEvent(ndk);
event.kind = 1;
event.content = "Hello world";
await event.sign();

Signer Relays

If the signer implements the getRelays() method, NDK will use the relays returned by that method as the explicit relays.

Combining signers

You can specify the use of a different signer to sign with different keys.

TIP

If you plan on allowing multiple signers we recommend using @nostr-dev-kit/sessions.

ts
import { NDKEvent, NDKPrivateKeySigner } from "@nostr-dev-kit/ndk";

const signer1 = NDKPrivateKeySigner.generate();
const pubkey1 = signer1.pubkey;

const event1 = new NDKEvent();
event1.kind = 1;
event1.content = "Hello world";
await event1.sign(signer1);

event1.pubkey === pubkey1; // true

const signer2 = NDKPrivateKeySigner.generate();
const pubkey2 = signer2.pubkey;

const event2 = new NDKEvent();
event2.kind = 1;
event2.content = "Hello world";
await event2.sign(signer2);

event2.pubkey === pubkey2; // true

Read Public key

Read the user's public key

ts
nip07signer.user().then(async (user) => {
    if (!!user.npub) {
        console.log("Permission granted to read their public key:", user.npub);
    }
});

Generate Keys

One good case where you would want to use NDKPrivateKeySigner is to help you generate keys as the signer provides helper methods.

This snippet demonstrates how to generate a new key pair and obtain all its various formats (private key, public key, nsec, npub).

ts
import { NDKPrivateKeySigner } from "@nostr-dev-kit/ndk";

// Generate a new private key
const signer = NDKPrivateKeySigner.generate();
const privateKey = signer.privateKey; // Get the hex private key
const publicKey = signer.pubkey; // Get the hex public key
const nsec = signer.nsec; // Get the private key in nsec format
const npub = signer.userSync.npub; // Get the public key in npub format

You can use these different formats for different purposes:

  • privateKey: Raw private key for cryptographic operations
  • publicKey: Raw public key (hex format) for verification
  • nsec: Encoded private key format (bech32) - used for secure sharing when needed
  • npub: Encoded public key format (bech32) - used for user identification

Encrypting Keys

For storing keys securely with password protection, use NIP-49 (ncryptsec format):

ts
import { NDKPrivateKeySigner } from "@nostr-dev-kit/ndk";

const signer = NDKPrivateKeySigner.generate();

// Encrypt with a password
const password = "user-chosen-password";
const ncryptsec = signer.encryptToNcryptsec(password);

// Store securely (e.g., localStorage)
localStorage.setItem("encrypted_key", ncryptsec);

const restoredButEncrypted = localStorage.getItem("encrypted_key");

if (restoredButEncrypted) {
    // Later, restore the signer
    const restoredSigner = NDKPrivateKeySigner.fromNcryptsec(restoredButEncrypted, password);

    console.log("Original pubkey:", signer.pubkey);
    console.log("Restored pubkey:", restoredSigner.pubkey);
}

See Encrypted Keys (NIP-49) for more examples and best practices.

Signer Persistence

In a lot of applications you would want to persist the signer preferences so the state can be maintained without requiring re-authentication.

Please consult the dedicated section about signer persistence.

Code Snippets

More snippets and examples can be found in the snippets directory