fix: agent identity key derivation mismatch across keygen/register/signing (musehub#99)
muse auth keygen --agent-id, muse auth register --agent-id, and resolve_signing_identity() (used by muse commit --sign --agent-id) each independently reconstructed an agent's Ed25519 identity key from the mnemonic + stored hd_path -- but two different, incompatible derivation algorithms were in play, so all three disagreed on what the actual key was for the same nominal identity.
Root cause: muse auth keygen's agent branch derives the real key via a two-stage KDF -- derive_agent_sub_seed(operator_seed, DOMAIN_IDENTITY, slot) to get a domain-scoped sub-seed, then derive_identity_key(sub_seed, hub=...) treating that sub-seed as if it were itself a fresh BIP39 seed (this is intentional: it's what lets an agent process injected with only the sub-seed via MUSE_AGENT_KEY_FD be physically unable to derive keys outside its own subtree). The hd_path string stored alongside the key (via muse_path()) is a human-readable coordinate label describing this derivation for display purposes -- it was never meant to be independently re-walked.
Both muse auth register (auth.py) and resolve_signing_identity() (identity.py, the function backing muse commit --sign --agent-id) did exactly that: treated the stored hd_path as a literal SLIP-10 path and fed it straight into derive_path(seed, hd_path) from the operator's ROOT seed -- a completely different derivation from the two-stage sub-seed KDF. Confirmed via two independent real repros (staging hub, zero passphrase interaction, fresh scratch repos): identical printed HD path, completely different fingerprints between keygen and register.
Fix: added muse.core.keypair.derive_agent_identity_key() as the single shared implementation of the two-stage KDF, and made every call site that reconstructs an agent's key from a stored entry use it instead of a raw derive_path(seed, hd_path) call: - muse auth keygen's agent branch now calls the shared helper directly (was: same logic duplicated inline). - muse auth register's re-derivation now branches on agent_id: agent entries use the shared helper; human entries keep the existing derive_path() walk (which is correct for them -- their hd_path really is a literal, walkable path). - resolve_signing_identity()'s _derive() closure now takes an explicit derive_agent_id parameter so it can do the same branch for both the agent-specific lookup and the human fallback lookup.
Verified end-to-end against the real fixed CLI (muse-dev, staging hub, disposable scratch repo, not canonical): keygen fingerprint = sha256:8f5c6a2c7188cd7b63c7221c2b63124b093be... register fingerprint = sha256:8f5c6a2c7188cd7b63c7221c2b63124b093be... (was different before the fix) commit --sign pubkey = ed25519:BYm7rjf9tGWzkOYtX62OOjxZ9B-kD03vTnmuuW1yZAY (matches keygen's pubkey exactly)
Added TestAgentIdKeygenRegisterKeyConsistency to test_cmd_auth_keygen_register.py: - test_keygen_and_register_report_the_same_fingerprint (musehub#99 regression) - test_resolve_signing_identity_matches_keygen_fingerprint (musehub#99 regression, covers the muse commit --sign --agent-id path specifically)
Both confirmed red before the fix (register/signing agreed with each other on the wrong key, disagreeing with keygen) and green after.
Full auth/identity/hdkeys/keypair/commit-signing surface re-run clean: 584/584 across 20 test files, zero regressions.
Out of scope, noted for awareness: muse auth register does not accept a BIP-39 passphrase argument at all (mnemonic_to_seed(mnemonic) with no passphrase), so an operator who used a non-empty passphrase during keygen would hit a similar-shaped mismatch for their OWN (human) key, not just agent keys. Not reproduced or fixed here -- episode 04's flow uses a blank passphrase throughout. Also out of scope: hub_scoping_migration.py, domain_migration.py, and migrate_cmd.py's derive_path() call sites -- one-time developer migration tools, not live user-facing paths, not audited for the same agent-entry issue in this pass.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Semantic Changes
8 symbols
Files Changed
~4
1200 in snapshot
0 comments
muse hub commit comment sha256:b88052fb32737a257bef0a57e45c9631eff7474cfbc5d18229be40481cf46daa --body "your comment"
No comments yet. Be the first to start the discussion.