Skip to content

Conversation

@mfw78
Copy link
Member

@mfw78 mfw78 commented Dec 8, 2025

Summary

  • Define comprehensive key derivation standards for Nexum
  • BIP-44 paths for account signing keys (on-card, non-exportable)
  • EIP-1581 paths for encryption and non-wallet keys (exportable)
  • Delegation key derivation from account addresses (for EIP-7702)
  • Domain-separated key derivation patterns for cryptographic isolation

Context

NXP-2 builds on NXP-1's identity model to define the exact derivation paths used for all key types. It distinguishes between signing keys (which remain on secure elements) and encryption/delegation keys (which must be exported for software operations).

Closes nxm-rs/nexum#86
Related to nxm-rs/pm#3 (EPIC 0.0)

Test plan

  • Verify Zola builds successfully
  • Review derivation path formulas for correctness
  • Confirm EIP-1581 references are accurate
  • Verify domain separation examples

Define comprehensive key derivation standards for Nexum:
- BIP-44 paths for account signing keys
- EIP-1581 paths for encryption and non-wallet keys
- Delegation key derivation from account addresses
- Domain-separated key derivation patterns

Closes nxm-rs/nexum#86
Related to nxm-rs/pm#3 (EPIC 0.0)
Comment on lines +212 to +246
@dataclass
class ExtendedKey:
private_key: int
chain_code: bytes

def try_derive(parent: ExtendedKey, index: int) -> ExtendedKey | None:
"""Attempt to derive a child key. Returns None if derivation is invalid."""
data = serialize(parent, index)
i = hmac.new(parent.chain_code, data, hashlib.sha512).digest()
i_l, i_r = i[:32], i[32:]

# Check validity per BIP-32
parsed_key = int.from_bytes(i_l, byteorder='big')
if parsed_key >= SECP256K1_ORDER:
return None # Invalid: I_L >= curve order

child_key = (parsed_key + parent.private_key) % SECP256K1_ORDER
if child_key == 0:
return None # Invalid: resulting key is zero

return ExtendedKey(private_key=child_key, chain_code=i_r)

def derive_with_retry(parent: ExtendedKey, start_index: int) -> tuple[ExtendedKey, int]:
"""Derive child key, incrementing index on failure until valid."""
index = start_index

while True:
result = try_derive(parent, index)
if result is not None:
return (result, index)
index += 1 # Increment and retry

# Safety check (should never be reached in practice)
if index > 0xFFFFFFFF:
raise ValueError("Exhausted index space")

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we dont need to show bip32 implementation in this nxp

```

Where:
- `44'` — BIP-44 purpose (hardened)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think its more interesting to set this 44 to a DELEGATING_PURPOSE instead that is defined in some other nxp for common delegation purposes.

|------|------------|-------|
| `m/44'/60'/*/0/*` | No | Standard accounts stay on-card |
| `m/44'/60'/*/1/*` | No | Delegation keys stay on-card |
| `m/43'/60'/1581'/*` | Yes | EIP-1581 encryption keys only |

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so maybe you want to explicitly DISALLOW identity index to be 1581 in nxp-1?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not an issue as 1581 for an identity index is fine as the identity index will be under the 44' purpose code, whereas for EIP-1581 derived keys, these fall under the 43' purpose code.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

docs: define derivation path standards

3 participants