gabriel / muse public
keypair.py python
97 lines 3.7 KB
Raw
sha256:81ae324db5ad375fbfe4834c6fcb378312cafad3cc92dec5d3e5c427306621a2 fix: remove commit_exists filter from have anchors — server… Sonnet 4.6 patch 21 days ago
1 """Ed25519 keypair utilities for Muse CLI authentication.
2
3 Keys are derived in memory from the OS keychain mnemonic via SLIP-0010 HD
4 derivation. No private key material is ever written to disk.
5
6 Public key encoding
7 --------------------
8 Public keys are transmitted as URL-safe base64 (no padding) of the raw 32-byte
9 Ed25519 public key material — identical to the format expected by MuseHub's
10 ``/api/auth/challenge`` and ``/api/auth/verify`` endpoints.
11
12 Fingerprint
13 -----------
14 ``sha256:<64-hex>`` of the raw 32 public key bytes — the canonical prefixed
15 format used throughout the Muse ecosystem and stored by MuseHub.
16 """
17
18 import base64
19 import logging
20
21 from cryptography.hazmat.primitives.asymmetric.ed25519 import (
22 Ed25519PrivateKey,
23 Ed25519PublicKey,
24 )
25 from cryptography.hazmat.primitives.serialization import (
26 Encoding,
27 PublicFormat,
28 )
29
30 from muse.core.paths import user_keys_dir as _user_keys_dir
31
32 logger = logging.getLogger(__name__)
33
34 _KEYS_DIR = _user_keys_dir()
35
36 # _KEYS_DIR is exported for use by cleanup-keys and security-check commands.
37
38 # ---------------------------------------------------------------------------
39 # Public key encoding helpers
40 # ---------------------------------------------------------------------------
41
42 def public_key_to_b64url(public_key: Ed25519PublicKey) -> str:
43 """Return the canonically prefixed encoding of the raw public key bytes.
44
45 Returns ``"ed25519:<base64url>"`` — the same self-describing format used
46 for all cryptographic values in the Muse ecosystem.
47 """
48 from muse.core.types import encode_pubkey, DEFAULT_SIGN_ALGO
49 raw: bytes = public_key.public_bytes(Encoding.Raw, PublicFormat.Raw)
50 return encode_pubkey(DEFAULT_SIGN_ALGO, raw)
51
52 def public_key_fingerprint(public_key: Ed25519PublicKey) -> str:
53 """Return the canonical ``sha256:<64-hex>`` fingerprint of the raw public key bytes."""
54 from muse.core.types import public_key_fingerprint as _fp # noqa: PLC0415
55 raw: bytes = public_key.public_bytes(Encoding.Raw, PublicFormat.Raw)
56 return _fp(raw)
57
58 # ---------------------------------------------------------------------------
59 # Public API
60 # ---------------------------------------------------------------------------
61
62 def sign_bytes(private_key: Ed25519PrivateKey, data: bytes) -> str:
63 """Sign *data* with *private_key* and return a canonically prefixed signature.
64
65 Args:
66 private_key: Ed25519 private key.
67 data: Raw bytes to sign (the nonce bytes from the challenge token).
68
69 Returns:
70 ``"ed25519:<base64url>"`` of the 64-byte signature — self-describing,
71 consistent with all other cryptographic values in the Muse ecosystem.
72 """
73 from muse.core.types import encode_sig, DEFAULT_SIGN_ALGO
74 signature: bytes = private_key.sign(data)
75 return encode_sig(DEFAULT_SIGN_ALGO, signature)
76
77 def derive_hd_public_info(seed: bytes, rotation_index: int = 0) -> tuple[str, str]:
78 """Derive the Ed25519 public key info from *seed* without writing any file.
79
80 Returns ``(public_key_b64url, fingerprint)`` derived via SLIP-0010 HD path.
81 The private key never leaves memory — no PEM is written.
82
83 Args:
84 seed: 64-byte BIP39 root seed from :func:`muse.core.bip39.mnemonic_to_seed`.
85 rotation_index: Key rotation counter (0 = primary key).
86 """
87 from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey as _Ed25519PrivateKey
88 from muse.core.hdkeys import derive_identity_key
89
90 dk = derive_identity_key(seed, index=rotation_index)
91 try:
92 private_key = _Ed25519PrivateKey.from_private_bytes(dk.private_bytes)
93 finally:
94 dk.zero()
95 public_key = private_key.public_key()
96 return public_key_to_b64url(public_key), public_key_fingerprint(public_key)
97
File History 4 commits
sha256:81ae324db5ad375fbfe4834c6fcb378312cafad3cc92dec5d3e5c427306621a2 fix: remove commit_exists filter from have anchors — server… Sonnet 4.6 patch 21 days ago
sha256:36c3cb3e76619d4c30a6d9bf81b5ec4ff148e30dcfed913e3114ca7b43b81c7e fix: rename objects→blobs in push client and all stale test… Sonnet 4.6 patch 22 days ago
sha256:c06a9b9b9fee26c68ea725b44d54b2c0a171301ce9de746d5b656617b4463a9a fix: repair four test failures from post-migration audit Sonnet 4.6 patch 29 days ago
sha256:1900655993c83c4107067375548a7be823e471d2515830842f1a12cba4bd3cdf fix: unified object store migration — idempotent writes, JS… Sonnet 4.6 minor 29 days ago