Skip to main content

Keys and Signing

Your identity in Nostr is a cryptographic keypair. This page explains how keys work and how events are signed.

Key Basics​

Nostr uses secp256k1 keys—the same curve used by Bitcoin. Your identity consists of:

ComponentDescriptionShare?
Private Key32-byte secret for signingNever
Public Key32-byte identifier derived from private keyFreely

Key Formats​

Keys are typically represented in two formats:

Hex format (raw):

Private: 3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d
Public: 7e7e9c42a91bfef19fa929e5fda1b72e0ebc1a4c1141673e2794234d86addf4e

Bech32 format (human-readable with prefix):

Private: nsec180cvv07tjdrrgpa0j7j7tmnyl2yr6yr7l8j4s3evf6u64th6gkwsyyh6vf
Public: npub10elfcs4fr0l0r8af98jlmgdh9c8tcxjvz9qkw038js35mp4dma8qzvjptg

The bech32 format includes a checksum and makes it clear whether you're looking at a public key (npub) or private key (nsec).

Generating Keys​

Using nostr-tools (JavaScript)​

import { generateSecretKey, getPublicKey } from 'nostr-tools/pure';
import { nip19 } from 'nostr-tools';

// Generate new keypair
const sk = generateSecretKey(); // Uint8Array
const pk = getPublicKey(sk); // hex string

// Convert to bech32 format
const nsec = nip19.nsecEncode(sk);
const npub = nip19.npubEncode(pk);

console.log('Private key (nsec):', nsec);
console.log('Public key (npub):', npub);

Using Python (pynostr)​

from nostr.key import PrivateKey

# Generate new keypair
private_key = PrivateKey()
public_key = private_key.public_key

print(f"Private key (hex): {private_key.hex()}")
print(f"Public key (hex): {public_key.hex()}")
print(f"Private key (bech32): {private_key.bech32()}")
print(f"Public key (bech32): {public_key.bech32()}")

Using Rust (nostr-sdk)​

use nostr::prelude::*;

fn main() {
// Generate new keypair
let keys = Keys::generate();

println!("Private key: {}", keys.secret_key().unwrap().to_bech32().unwrap());
println!("Public key: {}", keys.public_key().to_bech32().unwrap());
}

Event Signing​

Events must be signed to prove they came from the claimed author.

Signing Process​

  1. Create event object (without id and sig):
{
"pubkey": "author's public key (hex)",
"created_at": 1234567890,
"kind": 1,
"tags": [],
"content": "Hello, Nostr!"
}
  1. Serialize for hashing:
const serialized = JSON.stringify([
0, // Reserved for future use
event.pubkey, // Author's public key
event.created_at, // Unix timestamp
event.kind, // Event kind
event.tags, // Tags array
event.content // Content string
]);
  1. Compute SHA256 hash → this becomes the event id

  2. Sign the id with private key → this becomes the sig

Using nostr-tools​

import { finalizeEvent, generateSecretKey, getPublicKey } from 'nostr-tools/pure';

const sk = generateSecretKey();

const event = finalizeEvent({
kind: 1,
content: 'Hello, Nostr!',
tags: [],
created_at: Math.floor(Date.now() / 1000),
}, sk);

console.log('Signed event:', event);
// {
// id: '...',
// pubkey: '...',
// created_at: 1234567890,
// kind: 1,
// tags: [],
// content: 'Hello, Nostr!',
// sig: '...'
// }

Verification​

Anyone can verify an event's signature:

import { verifyEvent } from 'nostr-tools/pure';

const isValid = verifyEvent(event);
console.log('Signature valid:', isValid);

Verification confirms:

  • The event ID matches the hash of its contents
  • The signature is valid for the claimed public key

Key Security​

Keep Your Private Key Safe

Your private key is your identity. If someone gets it, they can impersonate you forever. There's no "password reset" in Nostr.

Best Practices​

  1. Never share your nsec - Treat it like a password
  2. Use a password manager - Store keys encrypted
  3. Consider hardware signing - NIP-07 browser extensions
  4. Backup securely - Multiple encrypted backups
  5. Use separate keys - Different keys for different purposes

NIP-07: Browser Extensions​

Instead of pasting your private key into apps, use a signing extension:

  • Alby - Popular Lightning + Nostr extension
  • nos2x - Minimal Nostr signer
  • Flamingo - Full-featured extension

These extensions:

  • Store your key securely
  • Sign events when apps request
  • Never expose your private key to websites
// App requests signature from extension
const event = {
kind: 1,
content: 'Hello from NIP-07!',
tags: [],
created_at: Math.floor(Date.now() / 1000),
};

// Extension handles signing
const signedEvent = await window.nostr.signEvent(event);

NIP-19: Bech32 Entities​

NIP-19 defines shareable identifiers:

PrefixDescription
npubPublic key
nsecPrivate key
noteEvent ID
nprofileProfile with relay hints
neventEvent with relay hints
naddrReplaceable event address
import { nip19 } from 'nostr-tools';

// Encode
const npub = nip19.npubEncode(publicKeyHex);
const nprofile = nip19.nprofileEncode({
pubkey: publicKeyHex,
relays: ['wss://relay.damus.io']
});

// Decode
const { type, data } = nip19.decode(npub);
console.log(type); // 'npub'
console.log(data); // hex public key

Next Steps​

Now that you understand keys: