BIP32: Define rules for derivation path parsing
As bitcoin/bips does not allow to create issues, I use this repository. I hope that's alright.
The format of BIP32 paths is something that has been discussed in multiple contexts, including recent implementation discussions (e.g., NBitcoin, embit) and different BIP PRs (BIP380 allows H, BIP380 disallows H).
I'd like to share the results of an attempted differential fuzzing campaign spanning several implementations using bitcoinfuzz. The differences were so significant that fuzzing was basically impossible and I resorted to manual testing. As seen in the table, the current version of BIP32 leaves too much flexibility in interpreting what is and isn't a valid path. This (again?) raises the question of why a standardized notation and invalid test vectors for BIP32 paths have not been defined yet. I wanted to share this mainly as a reference point for further discussion and a possible basis for constructing a set of valid and invalid BIP32 path test vectors. Feel free to add additional test cases.
Results concern only the path parsing API.
| Feature | Example | Bitcoin Core | bitcoinj | Rust Bitcoin | NBitcoin | Bitcoin-S | libwally-core | embit |
|---|---|---|---|---|---|---|---|---|
can start with m |
m/1 |
✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| can start with M | M/1 |
❌ | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ |
| can start with number | 1/1 |
✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ |
allows only m |
m |
✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ |
allows repeated m/ |
m/m/1 |
❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
| allows H for hardened index | m/1H |
❌ | ✅ | ❌ | ❌ | ❌ | ✅ | ✅ |
| allows h for hardened index | m/1h |
❌ | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ |
allows ' for hardened index |
m/1' |
✅ | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ |
| allows index > 0x7FFFFFFF | m/2147483648 |
✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ |
allows index > 0x7FFFFFFF with ' |
m/2147483648' |
✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ |
| allows index > 0xFFFFFFFF | m/4294967296 |
❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ |
| allows + before zero index | m/+0 |
❌ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ |
| allows - before zero index | m/-0 |
❌ | ✅ | ❌ | ✅ | ✅ | ❌ | ❌ |
| allows + before non-zero index | m/+1 |
❌ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ |
| allows - before non-zero index | m/-1 |
❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ |
| allows depth > 255 | m/1/1/.../1 |
✅ | ✅ | ✅ | ❌ | ✅ | ❌ | ✅ |
| allows empty path | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | |
| allows empty segment | m//1 |
❌ | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ |
| allows trailing / | m/1/ |
✅ | ✅ | ❌ | ✅ | ✅ | ❌ | ✅ |
| allows double trailing / | m/1// |
❌ | ✅ | ❌ | ✅ | ✅ | ❌ | ✅ |
| allows whitespace after index | m/1 |
❌ | ✅ | ❌ | ✅ | ✅ | ❌ | ✅ |
| allows whitespace before index | m/ 1 |
❌ | ✅ | ❌ | ✅ | ✅ | ❌ | ✅ |
| allows whitespace before m | m/1 |
❌ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ |