Chapter 2

Identity is a keypair

No accounts, no passwords: you are a secret key and its public key.

No sign-up form, no password, no username police. Let's forge you an identity in one click!

No sign-up form. Ever.

Every app you’ve used starts the same way: pick a username, choose a password, confirm your email. Behind the scenes, a company writes you into its database. Your account lives there, and it exists only as long as they say so.

Nostr skips all of that. Your identity is not a row in someone’s database: it’s a keypair, two big numbers that belong together:

  • a secret key (private key): 32 random bytes that only you know, and
  • a public key (pubkey): computed from the secret key, and safe to show the world.

Think of a mailbox with a slot. The public key is the address painted on the front: you put it on business cards so people can find you. The secret key is the only key that opens the box, and here it does something even better: it lets you stamp letters with a seal nobody else can forge.

No server hands these out. You make them yourself, on your own device, in a fraction of a second. Let’s do that right now.

The key forge

Forge a fresh keypair, then watch its hex bytes get re-spelled as an npub, one bech32 character at a time.

Sample key (public on purpose)
Secret key (hex)
88ce••••••••••••••••••••••••••••••••••••••••••••••••••••••••3858

32 random bytes. Whoever knows these IS you.

Public key (hex) 173c1c47ef97731d55e45f8b5df6c13c5685b06acdbeb0cf8077a82eb07d8091

Derived from the secret with secp256k1 math. Safe to share.

Hex → bech32, step by step

Encode which key?
1. Payload bytes
173c1c47ef97731d55e45f8b5df6c13c5685b06acdbeb0cf8077a82eb07d8091
2. Next 5 bits
3. Look up the bech32 alphabet
  1. 0 q
  2. 1 p
  3. 2 z
  4. 3 r
  5. 4 y
  6. 5 9
  7. 6 x
  8. 7 8
  9. 8 g
  10. 9 f
  11. 10 2
  12. 11 t
  13. 12 v
  14. 13 d
  15. 14 w
  16. 15 0
  17. 16 s
  18. 17 3
  19. 18 j
  20. 19 n
  21. 20 5
  22. 21 4
  23. 22 k
  24. 23 h
  25. 24 c
  26. 25 e
  27. 26 6
  28. 27 m
  29. 28 u
  30. 29 a
  31. 30 7
  32. 31 l
4. The result grows
npub1 npub1

The last 6 characters are a checksum: one typo and wallets will notice.

Step 1 of 59

Ready. Press play or step forward to encode the npub.

Press the button a few times. Every keypair is brand new: there are about 2256 possible secret keys, so the chance of two people ever rolling the same one is effectively zero. No registry is needed to keep keys unique: math does it for free.

Safety

Keys forged on this page are demo keys. They are generated in your browser and never saved or sent anywhere, but treat them as throwaway: don’t use them for a real identity.

Why can’t anyone work out my secret?

The public key is calculated from the secret key using an elliptic curve called secp256k1 (the same one Bitcoin uses). The useful property is that this calculation is a one-way street: going forward takes microseconds, going backward is hopeless.

Here’s a toy version you can actually see. Your secret is a number of hops; the public key is where you land on the clock. Try it, then try to work backwards.

The one-way clock

A toy version of the math behind secp256k1. Pick a secret number; the public spot is where you land after that many hops of 17 around a 61-position clock.

7 × 17

On a 61-spot clock, guessing backwards takes a blink. On the real curve there are about 1077 spots. Every computer on Earth guessing for the lifetime of the universe would not make a dent. That’s what lets you publish your public key everywhere and sleep soundly.

Under the hood: secp256k1 & x-only keys Show details

The real operation is scalar multiplication on an elliptic curve: P = k · G, where k is your secret key (a 256-bit integer between 1 and the curve order n − 1), G is the curve’s fixed generator point, and P is the public point. Recovering k from P is the elliptic-curve discrete logarithm problem, which has no known efficient solution.

NIP-01 says keys and signatures follow BIP-340 (Schnorr signatures on secp256k1). BIP-340 uses x-only public keys: only the 32-byte x coordinate is kept, and the y coordinate is implied to be even. That’s why a Nostr pubkey is exactly 64 lowercase hex characters, not the 66-character “compressed” keys you may know from elsewhere.

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

const sk = generateSecretKey(); // Uint8Array(32), from a CSPRNG
const pk = getPublicKey(sk); // 64 lowercase hex chars (x-only)
console.log(pk);

Not every 32-byte value is a valid key: the secret must be below n (≈ 1.158 × 1077), and a public key must be an x coordinate that is actually on the curve.

npub and nsec: same keys, friendlier spelling

Raw keys are written in hex: 64 characters of 0–9 and a–f. Computers love hex. Humans copy-pasting it into a chat… less so. Mix up one character and you’ve silently pointed at someone else (or no one).

So NIP-19 re-spells keys using bech32:

  • your public key becomes an npub: npub1…
  • your secret key becomes an nsec: nsec1…

The prefix tells you what you’re holding (so you notice when you’re about to paste a secret), the alphabet skips look-alike characters like 1, b, i and o, and the last six characters are a checksum: change any character and software will refuse it instead of quietly using the wrong key. Scroll back up to the forge and step through the encoding: the bytes are regrouped 5 bits at a time, and each 5-bit number picks one letter from a 32-letter alphabet.

Under the hood: NIP-19 encoding Show details
  1. Take the 32 payload bytes (256 bits).
  2. Regroup them into 5-bit words: 256 / 5 = 51.2, so 52 words, the last one padded with zero bits.
  3. Compute a 6-word BCH checksum over the prefix (the human-readable part, npub) and the words.
  4. Map all 58 words through the alphabet qpzry9x8gf2tvdw0s3jn54khce6mua7l and glue it together: hrp + "1" + chars. Result: 63 characters for every npub and nsec.

Bech32 strings are for humans only. NIP-19 is explicit: they must not be used inside events or relay messages. On the wire, it’s always hex (event trimmed to the interesting field):

{
  "kind": 1,
  "pubkey": "7e7e9c42a91bfef19fa929e5fda1b72e0ebc1a4c1141673e2794234d86addf4e",
  "content": "gm"
}
import * as nip19 from "nostr-tools/nip19";

const npub = nip19.npubEncode(pk); // "npub1…"
const nsec = nip19.nsecEncode(sk); // "nsec1…" (sk is a Uint8Array)
const { type, data } = nip19.decode(npub); // type === "npub", data === pk (hex)

NIP-19 also defines note (an event id), plus “shareable identifiers” that bundle extra hints as TLV (type-length-value) records: nprofile (pubkey + relays), nevent (event id + relays, optional author and kind) and naddr (kind + author + d tag of an addressable event). Try them all in the standalone key tool.

Two related NIPs: NIP-49 defines ncryptsec, a password-encrypted secret key and the recommended backup format, and NIP-06 derives keys from a BIP-39 seed phrase (path m/44'/1237'/<account>'/0/0). NIP-06 is now marked unrecommended in the NIPs index in favor of a single nsec, so treat it as legacy you may meet in older wallets.

Your key is your pen

Holding a secret key isn’t just about being someone: it’s about proving it. Every post you publish is a little JSON event carrying your public key and a signature, made with your secret key using the Schnorr scheme. Anyone (any relay, any app, any stranger) can check that signature with nothing but your public key. Nobody can fake it without your secret.

Try it with the key you forged above. Then change a single letter.

Signing is a stamp only you can make

A Schnorr signature proves the demo secret key approved exactly this message. Edit one letter after signing and watch the check fail.

Not signed yet.

That’s why Nostr doesn’t need a trusted server: the proof travels with the message. We’ll take a signed event apart byte by byte in the next chapter.

Under the hood: what actually gets signed Show details

Nostr never signs your raw text. The event is serialized as [0, pubkey, created_at, kind, tags, content], hashed with SHA-256 to get the 32-byte id, and the id is signed with BIP-340 Schnorr, producing a 64-byte sig (128 hex characters). Change the content and the id changes, so the old signature no longer matches.

import { finalizeEvent, verifyEvent } from "nostr-tools/pure";

const event = finalizeEvent(
  { kind: 1, created_at: Math.floor(Date.now() / 1000), tags: [], content: "gm" },
  sk,
); // adds pubkey, id and sig
verifyEvent(event); // true

Guard the nsec with your life (well, almost)

Here’s the flip side of “no company owns your account”: no company can rescue it either.

  • Anyone who gets your nsec is you. They can post, follow and DM as you, forever, and you can’t change “your password”, because there isn’t one.
  • If you lose your nsec with no backup, the identity is gone. That’s key loss, and it’s one of Nostr’s honest trade-offs (more in the last chapter).

Your npub, on the other hand, is meant to be shared: on your website, in your bio, on a T-shirt. Let’s practice telling the two apart.

Who gets which key?

Four people want something. Decide: share it or refuse?

  • Your friend Bob asks for your npub

    “What's your npub? I want to follow you!”

  • “Nostr Support” (DM) asks for your nsec

    “Your account is flagged. Send your nsec within 10 minutes to verify it.”

  • A podcast host asks for your npub

    “Can I put your npub in the show notes so listeners can find you?”

  • A shiny new web app asks for your nsec

    “Paste your nsec here to claim 21,000 free sats!”

Danger

Real apps never need you to paste your nsec. Use a signer instead: a browser extension (NIP-07) or a remote bunker (NIP-46) keeps the secret key in one place and only hands out signatures. Chapter 10 shows how.

Under the hood: key hygiene for developers Show details
  • Generate keys with a CSPRNG (generateSecretKey() uses crypto.getRandomValues), never with Math.random() or a hash of something guessable.
  • Keep secret keys as Uint8Array in memory, and don’t log them. Prefer delegating to a signer: window.nostr.signEvent() (NIP-07) or a bunker:// connection (NIP-46).
  • For backups, offer ncryptsec (NIP-49) instead of plain nsec text. Skip NIP-06 seed phrases for new code: the NIPs index marks them unrecommended in favor of a single nsec.
  • Accept both hex and NIP-19 input, but store and transmit hex; decode nostr: URIs (NIP-21) with the same NIP-19 decoder.

Quick check

What actually IS your identity on Nostr?

Choose one answer

Which one is safe to post publicly?

Choose one answer

You lose your nsec and have no backup. What happens?

Choose one answer

Takeaways

  • Your Nostr identity is a secp256k1 keypair: a secret key you keep and a public key you share.
  • The public key is computed from the secret key, but you can't run that math backwards.
  • npub and nsec are the same bytes as the hex keys, re-spelled in bech32 with a typo-catching checksum (NIP-19).
  • Signing proves an event came from your key; anyone can verify it with your public key alone.
  • Never share your nsec. There is no password reset: lose it or leak it, and that identity is gone.