Skip to content

BIP32 path parsing inconsistencies: negative indices, hardened overflow and input validation #113

Description

@kuliq23

While implementing a BIP32 path parse target for bitcoinfuzz. In embit, I encountered multiple inconsistencies and edge cases that lead to unexpected behavior or crashes.

These issues mainly stem from insufficient input validation in path segment parsing.

1. Negative index handling leads to runtime exception

Path segments are parsed using int(e), which allows negative values.

This leads to an OverflowError later during derivation in index.to_bytes(4, "big")

Example:

from embit import bip32
data = bytes.fromhex("deadbeef") 
root = bip32.HDKey.from_seed(data) 
root.derive("m/-1")

Result:

data = sec + index.to_bytes(4, "big")
             ^^^^^^^^^^^^^^^^^^^^^^^^
OverflowError: can't convert negative int to unsigned

Expected behavior:
Path parsing should reject negative indices early (e.g., ValueError)
(BIP32 indices must be in range [0, 2^32 - 1])


2. Hardened derivation with negative indices produces incorrect positive indices

Hardened indices are computed as:

if e[-1] in {"h", "H", "'"}:
        return int(e[:-1]) + HARDENED_INDEX

Due to negative values not being rejected early, negative values like -10h are allowed, resulting in:

(-10) + 2^31 = 2147483638

This is:

a valid positive integer
but not actually a hardened index, since it is < HARDENED_INDEX

Example:

from embit import bip32
data = bytes.fromhex("deadbeef") 
root = bip32.HDKey.from_seed(data) 
xpub = root.derive("m/-10h").to_public()
print(xpub.child_number == 2**31 - 10)

Result:
True

Impact:
A hardened derivation was indicated (via h), but a non-hardened derivation was performed.

Expected behavior:
Reject negative indices early


3. Insufficient validation of path segments (whitespace, empty segments, signs)

Path parsing does not validate or normalize input segments properly.

This leads to multiple edge cases:

Whitespace handling is inconsistent:
"m/1 h" → OK
"m/1h " → (trailing space breaks e[-1]) → ValueError
"m/\n1/2" → OK

Empty segments are not handled:
"m//1/" → produces ""e[-1]IndexError

Sign handling:
"m/+1/"→ OK
"m/-1" → accepted during parsing, but fails later (see issue 1)

Expected behavior:
Reject troublesome segments explicitly and consistently
Avoid runtime crashes (IndexError)
Validate each segment before accessing e[-1]
Reject explicit plus sign or document as allowed


These issues were discovered during work on bitcoinfuzz and may lead to:

  • crashes (potential for DoS attacks)
  • non-compliant derivation results
  • divergence from other BIP32 implementations behavior

Thank you for maintaining embit. I hope this report is of help.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions