8.1 KiB
Derivation Paths — A Simple Explanation
The seed
When you create a mnemonic (seed phrase), it generates a single master key. Think of it as the root of a tree — one key that controls everything below it.
The tree
From that master key, you can derive child keys. Each child key can have its own children, and so on. This creates a tree of keys, all derived from the same seed.
The path
A derivation path is just a set of directions for walking down the tree. It tells you which branches to take, starting from the master key (m).
m / 44' / 1237' / 0' / 0 / 0
Read it left to right:
| Segment | Meaning |
|---|---|
m |
The master key (your seed) |
44' |
Purpose: "this is a BIP-44 wallet" |
1237' |
Coin type: "this is Nostr" (1237 is Nostr's registered coin type) |
0' |
Account: "account #0" |
0 |
Change: "external/receive" (0) vs "internal/change" (1) |
0 |
Address index: "address #0" |
Each segment derives a child key from the parent. Change any segment and you get a completely different key.
The apostrophe (hardened vs unhardened)
The ' after a number means hardened. It's the most important detail in the path.
Without the apostrophe (unhardened)
m / 44' / 1237' / 0' / 0 / 0
^
no apostrophe = unhardened
Unhardened means: you can derive this child's public key from just the parent's public key — you don't need the private key.
This is useful for watch-only wallets: you can share the parent's extended public key with someone, and they can derive all the child public keys (addresses) without ever seeing your private key.
The risk: if a child private key leaks, and someone has the parent's extended public key, they can work backwards and derive all sibling private keys. So if address #5's private key leaks, addresses #0-4 and #6-99 are also compromised.
With the apostrophe (hardened)
m / 44' / 1237' / 0' / 0' / 0'
^
apostrophe = hardened
Hardened means: you need the parent's private key to derive this child. You cannot derive it from the public key alone.
The benefit: if a child private key leaks, the attacker cannot derive sibling keys. Each hardened child is isolated. Compromising one doesn't compromise the others.
Simple analogy
Imagine a building with floors and rooms:
- Unhardened = a glass door. Anyone with the floor key can see into all rooms on that floor. If someone picks the lock on room #5, they can figure out how to open rooms #0-4 and #6-99 too.
- Hardened = a steel door. You need the master floor key to open any room. Picking the lock on room #5 tells you nothing about the other rooms.
Visual: what happens when one key leaks
Unhardened (no apostrophe) — one leak compromises ALL siblings:
m/44'/1237'/0'/0/0 ✓ safe
m/44'/1237'/0'/0/1 ✓ safe
m/44'/1237'/0'/0/2 ✓ safe
m/44'/1237'/0'/0/3 ✓ safe
m/44'/1237'/0'/0/4 ✓ safe
m/44'/1237'/0'/0/5 ✗ COMPROMISED (leaked)
m/44'/1237'/0'/0/6 ✗ COMPROMISED (derived from leak + parent pubkey)
m/44'/1237'/0'/0/7 ✗ COMPROMISED (derived from leak + parent pubkey)
...
m/44'/1237'/0'/0/99 ✗ COMPROMISED (derived from leak + parent pubkey)
Parent extended public key (m/44'/1237'/0'/0) is public
+ one child private key (address #5) leaks
= ALL 100 sibling private keys are compromised
Hardened (with apostrophe) — one leak only affects that one key:
m/44'/1237'/0'/0'/0' ✓ safe
m/44'/1237'/0'/0'/1' ✓ safe
m/44'/1237'/0'/0'/2' ✓ safe
m/44'/1237'/0'/0'/3' ✓ safe
m/44'/1237'/0'/0'/4' ✓ safe
m/44'/1237'/0'/0'/5' ✗ COMPROMISED (leaked)
m/44'/1237'/0'/0'/6' ✓ safe (cannot be derived without parent PRIVATE key)
m/44'/1237'/0'/0'/7' ✓ safe (cannot be derived without parent PRIVATE key)
...
m/44'/1237'/0'/0'/99' ✓ safe (cannot be derived without parent PRIVATE key)
One child private key (address #5) leaks
= ONLY address #5 is compromised
= siblings are safe because hardened derivation requires the parent PRIVATE key
Why NIP-06 uses unhardened last segments
NIP-06 (Nostr's key derivation standard) uses m/44'/1237'/<account>'/0/0 — the first three segments are hardened, the last two are unhardened.
This is because NIP-06 copied the BIP-44 pattern from Bitcoin, where:
- The account segment is hardened (so different accounts are isolated)
- The change and address segments are unhardened (so watch-only wallets can derive addresses without the private key)
For Bitcoin, this makes sense: you want to share your extended public key with a payment processor so they can generate receive addresses for you.
For Nostr, it's less useful — but it means Nostr tools can derive your public keys from your extended public key, which some key management software uses.
What this means for n_signer
n_signer always holds your private key and derives everything itself. You never share extended public keys with anyone. So:
- Unhardened segments give you no benefit — you don't need watch-only derivation
- Unhardened segments add risk — the child key compromise vulnerability
Recommendation
Harden everything if you don't need NIP-06 compatibility:
m/44'/1237'/0'/0'/0' ← all hardened, maximum isolation
m/44'/1237'/0-99'/0'/0' ← all hardened, 100 isolated agent keys
Use NIP-06 paths if you want compatibility with standard Nostr tools:
m/44'/1237'/0'/0/0 ← NIP-06 standard (last two unhardened)
m/44'/1237'/0-99'/0/0 ← NIP-06 compatible, 100 agent keys
Common path patterns
Standard Nostr (NIP-06)
m/44'/1237'/0'/0/0
One key. The default Nostr key that tools like nak keygen produce.
Multiple Nostr accounts (NIP-06)
m/44'/1237'/0'/0/0 ← account 0
m/44'/1237'/1'/0/0 ← account 1
m/44'/1237'/2'/0/0 ← account 2
Change the account segment (hardened) to get different Nostr identities.
Multiple Nostr agents (hardened, maximum isolation)
m/44'/1237'/0'/0'/0' ← agent 0
m/44'/1237'/1'/0'/0' ← agent 1
m/44'/1237'/2'/0'/0' ← agent 2
Same as above but with the last two segments hardened. Each agent is fully isolated — compromising one doesn't compromise the others.
Range and wildcard syntax (n_signer wizard)
In n_signer's role wizard, you can use range syntax or wildcard for the variable segment:
m/44'/1237'/0-99'/0/0 ← agents 0-99, NIP-06 compatible
m/44'/1237'/0-99'/0'/0' ← agents 0-99, all hardened
m/44'/1237'/*'/0'/0' ← any agent index, all hardened (wildcard)
0-99'means "this segment can be any value from 0 to 99, hardened"*'means "this segment can be any non-negative integer, hardened" (wildcard — no range limit)*(without') means "any non-negative integer, unhardened"
The client specifies the exact path (e.g. m/44'/1237'/5'/0/0 for agent #5), and the server verifies it's within the role's allowed range (or accepts any value for *).
SSH keys (ed25519)
m/44'/102001'/0'/0'/0'
SLIP-0010 derivation for ed25519. All segments are hardened (SLIP-0010 requires this for ed25519).
Age / x25519 keys
m/44'/102002'/0'/0'/0'
Key agreement keys for Age encryption or X25519 ECDH.
Post-quantum keys
m/44'/102003'/0'/0'/0' ← ML-DSA-65 (signatures, FIPS 204)
m/44'/102004'/0'/0'/0' ← SLH-DSA-128s (signatures, FIPS 205)
m/44'/102005'/0'/0'/0' ← ML-KEM-768 (KEM, FIPS 203)
All hardened. The mnemonic-derived seed feeds a SHAKE-256 DRBG that replaces PQClean's randombytes() during keygen.
Summary
| Concept | Simple explanation |
|---|---|
m |
The master key (your seed) |
| Numbers | Which branch to take at each level |
' (apostrophe) |
"Hardened" — need private key to derive, isolates siblings |
No ' |
"Unhardened" — can derive from public key, but siblings can be compromised |
| Path | A set of directions from the master key to a specific key |
| Different path | Different key (always, no exceptions) |
Golden rule: If you don't need watch-only derivation (and n_signer doesn't), harden everything.