gabriel / muse public
Closed #1 Enhancement
filed by gabriel human · 38 days ago

feat: hash-derived domain integers with multi-sig registry

0 Anchors
Blast radius
Churn 30d
0 Proposals

Background

Muse's HD derivation path is:

m / 1075233755' / domain' / entity_type' / entity_id' / role' / index'

The purpose' level (1075233755) is already derived by hashing the string "muse"int.from_bytes(sha256(b"muse")[:4], "big") & 0x7FFFFFFF. This was a deliberate design choice made at Ava Labs to avoid the BIP44 coin-type registration war: instead of petitioning a committee for a number, you hash your project name and own that slot forever.

The domain' level previously used small hardcoded integers (0–6). This created the exact same registration-war problem for third-party domains. Phase 1 has shipped — all domain constants are now hash-derived. See implementation notes below.


Proposal: Hash-Derived Domain Integers + Multi-Sig Registry

The Core Insight

Every domain integer is derived from its canonical name string:

def domain_index(name: str) -> int:
    digest = hashlib.sha256(name.encode("utf-8")).digest()
    return int.from_bytes(digest[:4], "big") & 0x7FFFFFFF

Current canonical domain values (stable — do not change):

DOMAIN_IDENTITY   = domain_index("muse/identity")    # 1660078172
DOMAIN_PAYMENTS   = domain_index("muse/payments")    # 284229149
DOMAIN_CODE       = domain_index("muse/code")        # 678195575
DOMAIN_MUSIC      = domain_index("muse/music")       # 1755707987
DOMAIN_MIDI       = domain_index("muse/midi")        # 1444628350
DOMAIN_PROSE      = domain_index("muse/prose")       # 1658731548
DOMAIN_BLOCKCHAIN = domain_index("muse/blockchain")  # 1556829714
DOMAIN_GENERIC    = domain_index("muse/generic")     # 2023564266

A third-party domain requires zero permission:

DOMAIN_GAMING     = domain_index("acme/gaming")
DOMAIN_HEALTHCARE = domain_index("openhealth/records")
DOMAIN_LEGAL      = domain_index("lexcorp/contracts")

Why This Cannot Conflict

Two domains collide only if sha256(a)[:4] == sha256(b)[:4]. For 10,000 registered domains, birthday collision probability is ~0.0023%. A collision would be immediately visible in the registry and trivially resolved by appending a disambiguating suffix to the name string.


The Registry

The domain integer is self-certifying — it derives from the name, needs no external validation, and keys work on day one without registration. The registry is purely a human-readable annotation layer for tooling.

Structure

Dedicated Muse repo: gabriel/muse-domains on MuseHub. This repo has been created and pushed (Phase 2 complete).

muse-domains/
  registry.json          ← canonical name → index mapping
  schema.json            ← JSON Schema for registry entries
  governance.json        ← quorum policy
  GOVERNANCE.md          ← registration flow docs

registry.json format:

{
  "schema": 1,
  "domains": [
    {
      "name": "muse/identity",
      "index": 1660078172,
      "description": "MSign auth, MuseHub registration, cross-domain self",
      "owner": "gabriel",
      "registered_at": null
    }
  ]
}

registered_at will be the commit ID of the merge that added each entry — providing a tamper-evident timestamp and audit trail by construction.

Registration Flow

1. Anyone computes:
     index = domain_index("myorg/mydomain")

2. Keys derived at that index work immediately — no permission needed.

3. Optional: open a proposal on gabriel/muse-domains adding the entry.
   This is purely for discoverability — it does not gate key derivation.

4. Proposal is reviewed and merged by the quorum (see Governance).

5. Tooling annotates paths using the registry:
     m/1075233755'/1660078172'/...  →  "muse/identity"
   Unregistered domains display their raw integer — still functional.

Governance: Multi-Sig Quorum, Not a Single Signer

The muse-domains repo carries a governance.json at HEAD. Merging a proposal requires ≥ threshold approved reviews from declared quorum members.

{
  "schema": 1,
  "quorum": {
    "threshold": 1,
    "policy": "1-of-1",
    "members": ["sha256:d8faf8..."]
  }
}

MuseHub quorum enforcement is wired and shipped (Phase 2 complete). POST /repos/{repo_id}/proposals/{proposal_id}/merge returns 403 when governance.json exists but quorum is not met. 10 TDD tests green.

A merge commit to muse-domains is only valid if it carries ≥ threshold approvals from the declared member fingerprint set. Anyone can verify offline:

muse -C ~/ecosystem/muse-domains verify-commit HEAD --quorum --json
# → { "valid": true, "signatures": 1, "threshold": 1, "signers": [...] }

Tooling Changes

muse/core/hdkeys.pyDONE (Phase 1)

domain_index(name) added, all DOMAIN_* constants recomputed, exported in __all__. All tests updated and green.

Registry CLI — TODO (Phase 3)

# Compute index for any name (no registry needed)
muse domain index "myorg/mycustom" --json
# → { "name": "myorg/mycustom", "index": 2938471023, "registered": false }

# Look up a domain name
muse domain lookup "muse/music" --json

# Look up by index
muse domain lookup --index 1755707987 --json

# List all registered domains
muse domain list --json

# Check if a name is registered (exit 0/1)
muse domain check "acme/gaming" --json

Path annotation CLI — TODO (Phase 3)

# Annotate a raw HD path to human-readable form
muse path annotate "m/1075233755'/1660078172'/0'/0'/0'/0'" --json
# → { "purpose": "muse", "domain": "muse/identity", "entity_type": "human", ... }

Migration

Phase 1 triggered a breaking key migration. The integer values of all domain constants changed (e.g. DOMAIN_IDENTITY went from 0 to 1660078172). This was handled:

  1. muse auth recover re-derived identity key at the new HD path
  2. MuseHub local DB updated with the new fingerprint
  3. muse code migrate --force-resign re-signed all commits in both muse and musehub repos with the new key
  4. Remote tracking refs cleared manually (exposes a bug — see issue #20)

Note on musehub/services/musehub_repository.py: create_repo now passes "muse/generic" (not "") as the domain hash input for repos with no explicit domain, so compute_repo_id produces the correct hash-derived ID.


Implementation Phases

Phase 1 — Core ✅ DONE

  • domain_index(name: str) -> int added to muse/core/hdkeys.py
  • All DOMAIN_* constants recomputed, DOMAIN_GENERIC added
  • domain_index exported in __all__
  • All tests updated and green
  • musehub create_repo fixed to use "muse/generic"

Phase 2 — Registry repo + quorum enforcement ✅ DONE

  • gabriel/muse-domains repo created with registry.json, schema.json, governance.json, GOVERNANCE.md
  • musehub/services/musehub_governance.pyload_governance + check_quorum
  • proposals.py merge route blocks with 403 when quorum not met
  • 10 TDD tests covering all quorum paths — all green
  • Committed and pushed to musehub dev

Phase 3 — Tooling 🔲 TODO

  • muse domain index — compute index for any name
  • muse domain lookup — resolve name ↔ index via registry
  • muse domain list — enumerate registered domains
  • muse domain check — boolean registration check
  • muse path annotate — decode a raw HD path to human-readable form

Phase 4 — Migration tooling 🔲 TODO

  • muse migrate domain-integers — automated key re-derivation and hub re-registration for users upgrading from pre-Phase-1 installs
  • Deprecation warnings on old integer constants in the interim

Open Questions

  1. Name canonicalization — should "Muse/Music" and "muse/music" hash to the same index? Recommend: always lowercase, always UTF-8, reject uppercase at registration time.

  2. Collision detectionmuse domain index should warn if the computed index is already registered to a different name.

  3. Offline registry — should ~/.muse/ cache a local copy of the registry for air-gapped use? Probably yes — ship a snapshot with each Muse release, update on muse fetch.

  4. Registry forks — should muse domain list support --registry <url> to point at an alternate registry? Free if the registry is just a Muse repo.

Activity3
gabriel opened this issue 38 days ago
gabriel 38 days ago

Phase 3 complete — domain + path CLI commands shipped

All five commands from Phase 3 are now live on dev:

# Compute index for any name (no registry needed)
muse domain index "muse/music" --json
# → { "name": "muse/music", "index": 1755707987, "registered": true, ... }

# Look up by name
muse domain lookup "muse/identity" --json

# Reverse-lookup by integer
muse domain lookup --index 1660078172 --json

# List all locally registered domains
muse domain list --json
# → { "total": 8, "domains": [...] }

# Boolean registration check — exit 0 if registered, 1 if not
muse domain check "acme/gaming"
muse domain check "muse/code" --json

# Decode a raw HD path to human-readable form
muse path annotate "m/1075233755'/1660078172'/0'/0'/0'/0'"
# purpose:     muse (1075233755)
# domain:      muse/identity (1660078172)
# entity_type: human (0)
# entity_id:   0
# role:        sign (0)
# index:       0

Registry loading order:

  1. $MUSE_DOMAIN_REGISTRY env var (custom path for air-gapped use)
  2. ~/.muse/domain-registry.json (cached copy from hub)
  3. Built-in seed registry (all 8 first-party domains, ships with the binary)

Commit: sha256:53310e88ac8c

Remaining: Phase 4 — muse migrate domain-integers automated migration tooling.

gabriel 38 days ago

Status update — 2026-05-01

Completed this session

Phase 1 ✅ — Hash-derived domain constants

  • domain_index(name) added to muse/core/hdkeys.py
  • All DOMAIN_* constants recomputed from canonical name strings
  • DOMAIN_GENERIC added (was missing)
  • musehub create_repo fixed to pass "muse/generic" instead of ""

Phase 2 ✅ — Registry repo + quorum enforcement

  • gabriel/muse-domains repo created with registry.json, governance.json, schema.json, GOVERNANCE.md
  • musehub/services/musehub_governance.pyload_governance + check_quorum
  • Proposal merge route returns 403 when quorum not met
  • 10 TDD tests — all green

Phase 3 ✅ — Domain + path CLI commands

  • muse domain index <name> — compute hash-derived index for any name
  • muse domain lookup <name> / --index <n> — registry lookup + reverse lookup
  • muse domain list — enumerate all registered domains
  • muse domain check <name> — exit 0/1 registration check
  • muse path annotate <path> — decode raw HD path to human-readable form
  • Ships a seed registry of all 8 first-party domains for offline use

musehub bumped to v0.2.0rc5 — deployed to staging

Side fixes

  • Editable-install / install.sh incompatibility documented in install.py module docstring
  • Remote tracking ref bug noted in issue #20 (muse code migrate --force-resign must clear .muse/remotes/)

Remaining

Phase 4 🔲muse migrate domain-integers Automated migration command for users upgrading from pre-Phase-1 installs (where DOMAIN_IDENTITY was 0 instead of 1660078172). Should re-derive keys at the new HD paths and re-register with the hub.

gabriel 38 days ago

Phase 4 complete — muse migrate domain-integers shipped

All four phases are done. Issue #3 is fully closed.

What shipped

# Inspect — no writes
muse migrate domain-integers --dry-run --json

# Migrate + re-register with all hubs
muse migrate domain-integers --json

Core library (muse.core.domain_migration):

  • is_legacy_hd_path — detects pre-Phase-1 sequential domain integers (0–6)
  • new_path_for_legacy — maps old integer → new hash-derived HD path
  • derive_fingerprint_at_path — derives Ed25519 fingerprint at any HD path
  • scan_for_legacy / build_plans / run_migration — full migration driver

45 TDD tests — all green.

Commit: sha256:db00d09935852