Files
n_signer/documents/derivation_paths.md
T

228 lines
8.1 KiB
Markdown

# 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.