slip010.py
python
sha256:2a703f78341332ef0beb9856d2267de6aec89b3883c31519b6900b667d026e62
chore: delete muse/prose domain — hallucinated, never existed
Sonnet 4.6
minor
⚠ breaking
7 days ago
| 1 | """muse.core.slip010 — SLIP-0010 Ed25519 hierarchical deterministic key derivation. |
| 2 | |
| 3 | SLIP-0010 extends BIP32 HD key derivation to curves other than secp256k1. |
| 4 | Muse uses it exclusively for Ed25519 — the curve that powers MSign HTTP |
| 5 | authentication and off-chain MPay claims. |
| 6 | |
| 7 | Why SLIP-0010 instead of BIP32 for Ed25519? |
| 8 | -------------------------------------------- |
| 9 | BIP32's public-key child derivation relies on EC point addition, which is |
| 10 | well-defined for secp256k1. Ed25519 uses a different group (Curve25519 / |
| 11 | Edwards form) where the cofactor makes unhardened child derivation unsafe — |
| 12 | a leaked child key can reveal the parent private key. SLIP-0010 restricts |
| 13 | Ed25519 derivation to *hardened-only* (index ≥ 2³¹), eliminating the |
| 14 | vulnerability while keeping the same HMAC-SHA512 core as BIP32. |
| 15 | |
| 16 | Algorithm |
| 17 | --------- |
| 18 | All index values must satisfy ``index >= 0x80000000`` (hardened). |
| 19 | |
| 20 | **Master key from BIP39 seed**:: |
| 21 | |
| 22 | I = HMAC-SHA512(key=b"ed25519 seed", data=bip39_seed) |
| 23 | sk_bytes = I[:32] # 256-bit private scalar |
| 24 | chain = I[32:] # 256-bit chain code |
| 25 | |
| 26 | **Child key derivation (hardened only)**:: |
| 27 | |
| 28 | I = HMAC-SHA512(key=parent_chain, data=b"\\x00" + parent_sk + index.to_bytes(4, "big")) |
| 29 | child_sk = I[:32] |
| 30 | child_chain = I[32:] |
| 31 | |
| 32 | **Path notation**: ``m/1075233755'/0'/0'/0'/0'/0'`` |
| 33 | - ``m`` — master key (derived from seed) |
| 34 | - Each component ``n'`` — hardened index (``n + 0x80000000``) |
| 35 | - Muse purpose: **1 075 233 755** = ``int.from_bytes(sha256(b"muse")[:4], "big") & 0x7FFFFFFF`` |
| 36 | |
| 37 | Muse HD path structure (Ed25519) |
| 38 | ---------------------------------- |
| 39 | :: |
| 40 | |
| 41 | m / 1075233755' / domain' / entity_type' / entity_id' / role' / index' |
| 42 | │ │ │ │ │ └── Key rotation index |
| 43 | │ │ │ │ └─────────── Role (0'=sign, 1'=receive, 2'=provision, 3'=attest, 4'=delegate) |
| 44 | │ │ │ └───────────────────────── Entity ID (0'=first, 1'=second, …) |
| 45 | │ │ └───────────────────────────────────────── Entity type (0'=human, 1'=agent, 2'=org) |
| 46 | │ └──────────────────────────────────────────────────── Domain (0'=identity, 1'=payments, 2'=code, 3'=music, 4'=midi, 5'=prose, 6'=blockchain, …) |
| 47 | └──────────────────────────────────────────────────────────────────── Purpose (all hardened) |
| 48 | |
| 49 | Purpose derivation:: |
| 50 | |
| 51 | sha256(b"muse") = 0x4016c3db... |
| 52 | first 4 bytes = 0x4016c3db |
| 53 | & 0x7FFFFFFF = 1_075_233_755 |
| 54 | |
| 55 | Reproducible by anyone: ``int.from_bytes(hashlib.sha256(b"muse").digest()[:4], "big") & 0x7FFFFFFF`` |
| 56 | |
| 57 | Security properties |
| 58 | ------------------- |
| 59 | - All Ed25519 derivation is hardened: a leaked child key **cannot** reveal the |
| 60 | parent key or any sibling key (SLIP-0010 §3, contrast with BIP32 unhardened). |
| 61 | - The master key material never leaves this module — callers receive |
| 62 | :class:`DerivedKey` objects, not raw bytes. |
| 63 | - HMAC-SHA512 is provided by the ``cryptography`` library (OpenSSL bindings, |
| 64 | FIPS-validated on supported platforms). |
| 65 | |
| 66 | References |
| 67 | ---------- |
| 68 | - SLIP-0010: https://github.com/satoshilabs/slips/blob/master/slip-0010.md |
| 69 | - BIP32: https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki |
| 70 | |
| 71 | Examples |
| 72 | -------- |
| 73 | :: |
| 74 | |
| 75 | from muse.core.bip39 import mnemonic_to_seed |
| 76 | from muse.core.slip010 import master_key, derive_path, to_ed25519_private_key |
| 77 | |
| 78 | seed = mnemonic_to_seed("abandon abandon abandon abandon abandon abandon " |
| 79 | "abandon abandon abandon abandon abandon about") |
| 80 | |
| 81 | # Derive identity key at m/1075233755'/0'/0'/0'/0'/0' |
| 82 | dk = derive_path(seed, "m/1075233755'/0'/0'/0'/0'/0'") |
| 83 | private_key = to_ed25519_private_key(dk) |
| 84 | public_key = private_key.public_key() |
| 85 | """ |
| 86 | |
| 87 | import hmac |
| 88 | import hashlib |
| 89 | import re |
| 90 | from dataclasses import dataclass |
| 91 | from typing import TYPE_CHECKING |
| 92 | |
| 93 | if TYPE_CHECKING: |
| 94 | from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey |
| 95 | |
| 96 | __all__ = [ |
| 97 | "Slip010Error", |
| 98 | "SecretByteArray", |
| 99 | "DerivedKey", |
| 100 | "MUSE_PURPOSE", |
| 101 | "HARDENED_OFFSET", |
| 102 | "master_key", |
| 103 | "child_key", |
| 104 | "derive_path", |
| 105 | "to_ed25519_private_key", |
| 106 | "hardened", |
| 107 | "parse_path", |
| 108 | ] |
| 109 | |
| 110 | # --------------------------------------------------------------------------- |
| 111 | # Constants |
| 112 | # --------------------------------------------------------------------------- |
| 113 | |
| 114 | #: The hardened index offset. Any index >= this value is hardened. |
| 115 | HARDENED_OFFSET: int = 0x80000000 |
| 116 | |
| 117 | #: Muse-specific Ed25519 purpose index (unhardened form; add HARDENED_OFFSET when deriving). |
| 118 | #: Value: 1_075_233_755 = int.from_bytes(sha256(b"muse")[:4], "big") & 0x7FFFFFFF |
| 119 | #: Derivation: sha256(b"muse") = 0x4016c3db... → first 4 bytes & 0x7FFFFFFF = 1_075_233_755 |
| 120 | MUSE_PURPOSE: int = 1_075_233_755 |
| 121 | |
| 122 | #: HMAC key used for SLIP-0010 Ed25519 master key derivation (per spec). |
| 123 | _SLIP010_ED25519_KEY = b"ed25519 seed" |
| 124 | |
| 125 | # --------------------------------------------------------------------------- |
| 126 | # Errors |
| 127 | # --------------------------------------------------------------------------- |
| 128 | |
| 129 | class Slip010Error(ValueError): |
| 130 | """Raised when SLIP-0010 derivation fails. |
| 131 | |
| 132 | Common causes: |
| 133 | - Unhardened index passed to an Ed25519 derivation function. |
| 134 | - Malformed path string (e.g. ``m/0/1`` instead of ``m/0'/1'``). |
| 135 | - Seed too short (must be at least 16 bytes). |
| 136 | |
| 137 | Subclasses :class:`ValueError` so callers that catch ``ValueError`` still |
| 138 | work. Use ``except Slip010Error`` for precise handling. |
| 139 | |
| 140 | Examples |
| 141 | -------- |
| 142 | :: |
| 143 | |
| 144 | try: |
| 145 | child_key(parent_sk, parent_chain, 0) # unhardened → error |
| 146 | except Slip010Error as exc: |
| 147 | print(f"derivation error: {exc}") |
| 148 | """ |
| 149 | |
| 150 | # --------------------------------------------------------------------------- |
| 151 | # Data types |
| 152 | # --------------------------------------------------------------------------- |
| 153 | |
| 154 | class SecretByteArray(bytearray): |
| 155 | """A ``bytearray`` that zeroes itself when garbage-collected. |
| 156 | |
| 157 | Use this for sensitive byte buffers that cross function boundaries — agent |
| 158 | sub-seeds, intermediate secrets — where you cannot guarantee the caller will |
| 159 | manually zero the buffer. ``__del__`` is a best-effort safety net; always |
| 160 | call :meth:`zero` explicitly when you are done, since GC timing is not |
| 161 | guaranteed. |
| 162 | |
| 163 | Examples |
| 164 | -------- |
| 165 | :: |
| 166 | |
| 167 | sub = SecretByteArray(seed_bytes) |
| 168 | # ... use sub ... |
| 169 | sub.zero() # explicit best-practice |
| 170 | # __del__ fires on GC as a backstop |
| 171 | """ |
| 172 | |
| 173 | def zero(self) -> None: |
| 174 | """Overwrite the buffer with null bytes.""" |
| 175 | self[:] = b"\x00" * len(self) |
| 176 | |
| 177 | def __del__(self) -> None: |
| 178 | try: |
| 179 | self.zero() |
| 180 | except Exception: |
| 181 | pass |
| 182 | |
| 183 | @dataclass(slots=True) |
| 184 | class DerivedKey: |
| 185 | """An Ed25519 private key with its SLIP-0010 chain code. |
| 186 | |
| 187 | Both fields are 32 bytes. The chain code is required to derive child keys; |
| 188 | it acts as a second secret that prevents child key derivation without |
| 189 | knowledge of the parent. |
| 190 | |
| 191 | Attributes |
| 192 | ---------- |
| 193 | private_bytes: |
| 194 | 32-byte Ed25519 private scalar as a mutable ``bytearray``. |
| 195 | Feed into :func:`to_ed25519_private_key` to obtain a usable signing key. |
| 196 | Call :meth:`zero` immediately after the key object has been created. |
| 197 | chain_code: |
| 198 | 32-byte SLIP-0010 chain code as a mutable ``bytearray``. |
| 199 | Required for further child derivation. |
| 200 | Guard it with the same care as the private key. |
| 201 | |
| 202 | Security |
| 203 | -------- |
| 204 | Fields are ``bytearray`` so they can be overwritten after use. |
| 205 | Always call :meth:`zero` once the derived key material is no longer needed. |
| 206 | Do not store instances in logs or error messages — they contain private key |
| 207 | material. |
| 208 | |
| 209 | Python memory-zeroing boundary |
| 210 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 211 | ``DerivedKey`` is the zeroing boundary for this implementation. We control |
| 212 | these ``bytearray`` buffers directly, so :meth:`zero` genuinely overwrites |
| 213 | the key bytes in-place. |
| 214 | |
| 215 | Bytes returned by C extensions — ``hmac.digest()``, PEM serialisation, |
| 216 | file reads — are immutable ``bytes`` objects; Python provides no way to |
| 217 | zero them. We minimise the exposure window (use-then-discard immediately), |
| 218 | but cannot eliminate residual copies in the allocator's free list. |
| 219 | |
| 220 | **This is a known Python runtime limitation.** The Rust port will use the |
| 221 | ``zeroize`` crate, which derives a ``Zeroize`` trait that zeroes memory on |
| 222 | ``Drop`` with a compiler fence preventing the optimizer from eliding the |
| 223 | write. Until then, do not treat this implementation as suitable for |
| 224 | mission-critical production use where key material must be provably erased. |
| 225 | |
| 226 | Examples |
| 227 | -------- |
| 228 | :: |
| 229 | |
| 230 | dk = master_key(seed) |
| 231 | assert len(dk.private_bytes) == 32 |
| 232 | dk.zero() # wipe after use |
| 233 | """ |
| 234 | |
| 235 | private_bytes: bytearray |
| 236 | chain_code: bytearray |
| 237 | |
| 238 | def zero(self) -> None: |
| 239 | """Overwrite both fields with null bytes to remove key material from memory.""" |
| 240 | self.private_bytes[:] = b"\x00" * len(self.private_bytes) |
| 241 | self.chain_code[:] = b"\x00" * len(self.chain_code) |
| 242 | |
| 243 | def __del__(self) -> None: |
| 244 | """Safety-net zeroing — fires when the object is garbage-collected. |
| 245 | |
| 246 | Callers must still call :meth:`zero` explicitly (ideally in a |
| 247 | ``try/finally`` block) because GC timing is not guaranteed. This |
| 248 | method is the last line of defence against forgotten zeroing. |
| 249 | """ |
| 250 | try: |
| 251 | self.zero() |
| 252 | except Exception: |
| 253 | pass |
| 254 | |
| 255 | def __repr__(self) -> str: |
| 256 | """Redact key material from repr to prevent accidental logging.""" |
| 257 | return ( |
| 258 | f"DerivedKey(private_bytes=<redacted 32 bytes>, " |
| 259 | f"chain_code=<redacted 32 bytes>)" |
| 260 | ) |
| 261 | |
| 262 | # --------------------------------------------------------------------------- |
| 263 | # Core derivation primitives |
| 264 | # --------------------------------------------------------------------------- |
| 265 | |
| 266 | def master_key(seed: bytes) -> DerivedKey: |
| 267 | """Derive the SLIP-0010 Ed25519 master key from a BIP39 seed. |
| 268 | |
| 269 | Implements:: |
| 270 | |
| 271 | I = HMAC-SHA512(key=b"ed25519 seed", data=seed) |
| 272 | master_private_bytes = I[:32] |
| 273 | master_chain_code = I[32:] |
| 274 | |
| 275 | Parameters |
| 276 | ---------- |
| 277 | seed: |
| 278 | 64-byte BIP39 seed (output of :func:`muse.core.bip39.mnemonic_to_seed`). |
| 279 | Must be at least 16 bytes; shorter inputs are rejected. |
| 280 | |
| 281 | Returns |
| 282 | ------- |
| 283 | DerivedKey |
| 284 | Master private key and chain code. This is the root of the Ed25519 HD |
| 285 | key tree — guard it as carefully as the mnemonic itself. |
| 286 | |
| 287 | Raises |
| 288 | ------ |
| 289 | Slip010Error |
| 290 | If *seed* is shorter than 16 bytes. |
| 291 | |
| 292 | Security |
| 293 | -------- |
| 294 | HMAC-SHA512 is provided by Python's ``hmac`` + ``hashlib`` modules, which |
| 295 | use the ``cryptography`` / OpenSSL backend. The hardcoded key |
| 296 | ``b"ed25519 seed"`` is specified by SLIP-0010 and must not be changed. |
| 297 | |
| 298 | Examples |
| 299 | -------- |
| 300 | :: |
| 301 | |
| 302 | from muse.core.bip39 import mnemonic_to_seed |
| 303 | seed = mnemonic_to_seed("abandon " * 11 + "about") |
| 304 | dk = master_key(seed) |
| 305 | assert len(dk.private_bytes) == 32 |
| 306 | """ |
| 307 | if len(seed) < 16: |
| 308 | raise Slip010Error( |
| 309 | f"Seed must be at least 16 bytes; got {len(seed)}. " |
| 310 | "Use muse.core.bip39.mnemonic_to_seed to generate a valid 64-byte seed." |
| 311 | ) |
| 312 | I = bytearray(hmac.new(_SLIP010_ED25519_KEY, seed, hashlib.sha512).digest()) |
| 313 | dk = DerivedKey(private_bytes=bytearray(I[:32]), chain_code=bytearray(I[32:])) |
| 314 | I[:] = b"\x00" * 64 |
| 315 | return dk |
| 316 | |
| 317 | def child_key(parent: DerivedKey, index: int) -> DerivedKey: |
| 318 | """Derive a hardened SLIP-0010 Ed25519 child key. |
| 319 | |
| 320 | SLIP-0010 Ed25519 supports **hardened derivation only** (index ≥ 2³¹). |
| 321 | Passing an unhardened index is a hard error — it would be cryptographically |
| 322 | unsafe for Ed25519 and is not allowed by the specification. |
| 323 | |
| 324 | Implements:: |
| 325 | |
| 326 | data = b"\\x00" + parent.private_bytes + index.to_bytes(4, "big") |
| 327 | I = HMAC-SHA512(key=parent.chain_code, data=data) |
| 328 | child_private_bytes = I[:32] |
| 329 | child_chain_code = I[32:] |
| 330 | |
| 331 | Parameters |
| 332 | ---------- |
| 333 | parent: |
| 334 | Parent :class:`DerivedKey` (master or any previously derived key). |
| 335 | index: |
| 336 | Hardened child index. Must satisfy ``index >= 0x80000000`` (2³¹). |
| 337 | Use the :func:`hardened` helper to construct hardened indices from |
| 338 | human-friendly numbers, e.g. ``hardened(703)`` for ``703'``. |
| 339 | |
| 340 | Returns |
| 341 | ------- |
| 342 | DerivedKey |
| 343 | Child private key and chain code. |
| 344 | |
| 345 | Raises |
| 346 | ------ |
| 347 | Slip010Error |
| 348 | If *index* is not hardened (< 2³¹). |
| 349 | |
| 350 | Security |
| 351 | -------- |
| 352 | Hardened derivation means that knowledge of the child private key (and |
| 353 | chain code) does **not** reveal the parent private key. This is the |
| 354 | key security property that makes SLIP-0010 safe for Ed25519. |
| 355 | |
| 356 | Examples |
| 357 | -------- |
| 358 | :: |
| 359 | |
| 360 | dk = master_key(seed) |
| 361 | child = child_key(dk, hardened(703)) # m/703' |
| 362 | grandchild = child_key(child, hardened(0)) # m/703'/0' |
| 363 | """ |
| 364 | if index < HARDENED_OFFSET: |
| 365 | raise Slip010Error( |
| 366 | f"SLIP-0010 Ed25519 only supports hardened child derivation. " |
| 367 | f"Index {index} is not hardened (must be >= {HARDENED_OFFSET:#010x}). " |
| 368 | f"Use hardened({index}) to derive the hardened variant." |
| 369 | ) |
| 370 | data = bytearray(b"\x00") + parent.private_bytes + index.to_bytes(4, "big") |
| 371 | I = bytearray(hmac.new(parent.chain_code, data, hashlib.sha512).digest()) |
| 372 | dk = DerivedKey(private_bytes=bytearray(I[:32]), chain_code=bytearray(I[32:])) |
| 373 | I[:] = b"\x00" * 64 |
| 374 | data[:] = b"\x00" * len(data) |
| 375 | return dk |
| 376 | |
| 377 | # --------------------------------------------------------------------------- |
| 378 | # Path derivation |
| 379 | # --------------------------------------------------------------------------- |
| 380 | |
| 381 | _PATH_RE = re.compile(r"^m(/\d+')+$") |
| 382 | _COMPONENT_RE = re.compile(r"(\d+)'") |
| 383 | |
| 384 | def parse_path(path: str) -> list[int]: |
| 385 | """Parse a SLIP-0010 hardened-only path string into a list of absolute indices. |
| 386 | |
| 387 | All components must be hardened (``'`` suffix required). Unhardened |
| 388 | components are rejected because SLIP-0010 Ed25519 does not support them. |
| 389 | |
| 390 | Parameters |
| 391 | ---------- |
| 392 | path: |
| 393 | Derivation path in standard notation, e.g. ``"m/703'/0'/0'/0'"``. |
| 394 | Must start with ``"m/"`` and contain only hardened components. |
| 395 | |
| 396 | Returns |
| 397 | ------- |
| 398 | list[int] |
| 399 | Absolute child indices (each >= ``HARDENED_OFFSET``), in derivation order. |
| 400 | E.g. ``"m/703'/0'/0'/0'"`` → ``[703+2³¹, 0+2³¹, 0+2³¹, 0+2³¹]``. |
| 401 | |
| 402 | Raises |
| 403 | ------ |
| 404 | Slip010Error |
| 405 | If *path* is malformed, empty, or contains any unhardened component. |
| 406 | |
| 407 | Examples |
| 408 | -------- |
| 409 | :: |
| 410 | |
| 411 | indices = parse_path("m/703'/0'/0'/0'") |
| 412 | assert indices == [703 + HARDENED_OFFSET, HARDENED_OFFSET, HARDENED_OFFSET, HARDENED_OFFSET] |
| 413 | |
| 414 | parse_path("m/0/1") # raises Slip010Error — unhardened components |
| 415 | """ |
| 416 | path = path.strip() |
| 417 | if not _PATH_RE.match(path): |
| 418 | raise Slip010Error( |
| 419 | f"Invalid SLIP-0010 path: {path!r}. " |
| 420 | "Path must be of the form 'm/n1'/n2'/...' with all-hardened components. " |
| 421 | "Example: \"m/703'/0'/0'/0'\"" |
| 422 | ) |
| 423 | return [int(m) + HARDENED_OFFSET for m in _COMPONENT_RE.findall(path)] |
| 424 | |
| 425 | def derive_path(seed: bytes, path: str) -> DerivedKey: |
| 426 | """Derive an Ed25519 key at *path* from a BIP39 *seed*. |
| 427 | |
| 428 | This is the primary high-level entry point for key derivation. It |
| 429 | combines :func:`master_key` with repeated :func:`child_key` calls |
| 430 | for each component of *path*. |
| 431 | |
| 432 | All path components must be hardened (``'`` suffix). This is a hard |
| 433 | constraint of SLIP-0010 Ed25519 — see the module docstring for why. |
| 434 | |
| 435 | Parameters |
| 436 | ---------- |
| 437 | seed: |
| 438 | 64-byte BIP39 seed (from :func:`muse.core.bip39.mnemonic_to_seed`). |
| 439 | path: |
| 440 | Derivation path, e.g. ``"m/703'/0'/0'/0'"``. |
| 441 | |
| 442 | Returns |
| 443 | ------- |
| 444 | DerivedKey |
| 445 | Ed25519 private key and chain code at the requested path. |
| 446 | |
| 447 | Raises |
| 448 | ------ |
| 449 | Slip010Error |
| 450 | If *path* is malformed or *seed* is too short. |
| 451 | |
| 452 | Performance |
| 453 | ----------- |
| 454 | Each path component requires one HMAC-SHA512 call. A 4-component path |
| 455 | (``m/703'/0'/0'/0'``) takes < 1 ms on modern hardware. Keys may be |
| 456 | cached in-process but must never be written to disk in raw form. |
| 457 | |
| 458 | Examples |
| 459 | -------- |
| 460 | :: |
| 461 | |
| 462 | from muse.core.bip39 import mnemonic_to_seed |
| 463 | from muse.core.slip010 import derive_path, to_ed25519_private_key |
| 464 | |
| 465 | seed = mnemonic_to_seed("abandon " * 11 + "about") |
| 466 | |
| 467 | # Human operator MSign identity |
| 468 | dk = derive_path(seed, "m/703'/0'/0'/0'") |
| 469 | |
| 470 | # Agent slot 1 MSign identity |
| 471 | dk_agent = derive_path(seed, "m/703'/1'/0'/0'") |
| 472 | """ |
| 473 | indices = parse_path(path) |
| 474 | dk = master_key(seed) |
| 475 | for index in indices: |
| 476 | next_dk = child_key(dk, index) |
| 477 | dk.zero() # wipe intermediate key — child no longer needs parent material |
| 478 | dk = next_dk |
| 479 | return dk |
| 480 | |
| 481 | # --------------------------------------------------------------------------- |
| 482 | # Key materialisation |
| 483 | # --------------------------------------------------------------------------- |
| 484 | |
| 485 | def to_ed25519_private_key(dk: DerivedKey) -> "Ed25519PrivateKey": |
| 486 | """Materialise a :class:`DerivedKey` as a ``cryptography`` Ed25519 private key. |
| 487 | |
| 488 | The returned key object can sign bytes directly:: |
| 489 | |
| 490 | private_key = to_ed25519_private_key(dk) |
| 491 | signature = private_key.sign(message) |
| 492 | |
| 493 | And expose the public key:: |
| 494 | |
| 495 | public_key = private_key.public_key() |
| 496 | pub_bytes = public_key.public_bytes_raw() # 32 bytes |
| 497 | |
| 498 | Parameters |
| 499 | ---------- |
| 500 | dk: |
| 501 | :class:`DerivedKey` from :func:`master_key`, :func:`child_key`, or |
| 502 | :func:`derive_path`. |
| 503 | |
| 504 | Returns |
| 505 | ------- |
| 506 | Ed25519PrivateKey |
| 507 | A ``cryptography`` library Ed25519 private key, ready for signing. |
| 508 | Compatible with all MSign operations in :mod:`muse.core.msign`. |
| 509 | |
| 510 | Examples |
| 511 | -------- |
| 512 | :: |
| 513 | |
| 514 | dk = derive_path(seed, "m/703'/0'/0'/0'") |
| 515 | private_key = to_ed25519_private_key(dk) |
| 516 | public_bytes = private_key.public_key().public_bytes_raw() |
| 517 | assert len(public_bytes) == 32 |
| 518 | """ |
| 519 | from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey |
| 520 | return Ed25519PrivateKey.from_private_bytes(dk.private_bytes) |
| 521 | |
| 522 | # --------------------------------------------------------------------------- |
| 523 | # Index helpers |
| 524 | # --------------------------------------------------------------------------- |
| 525 | |
| 526 | def hardened(n: int) -> int: |
| 527 | """Return the hardened form of index *n* (adds the hardened offset 2³¹). |
| 528 | |
| 529 | Parameters |
| 530 | ---------- |
| 531 | n: |
| 532 | Unhardened index (0 to 2³¹ − 1). |
| 533 | |
| 534 | Returns |
| 535 | ------- |
| 536 | int |
| 537 | ``n + 0x80000000``. Pass this to :func:`child_key`. |
| 538 | |
| 539 | Raises |
| 540 | ------ |
| 541 | Slip010Error |
| 542 | If *n* is negative or already >= ``HARDENED_OFFSET``. |
| 543 | |
| 544 | Examples |
| 545 | -------- |
| 546 | :: |
| 547 | |
| 548 | assert hardened(703) == 703 + 0x80000000 |
| 549 | assert hardened(0) == 0x80000000 |
| 550 | """ |
| 551 | if n < 0 or n >= HARDENED_OFFSET: |
| 552 | raise Slip010Error( |
| 553 | f"Index {n} is out of range for hardened() — must be 0 ≤ n < {HARDENED_OFFSET}." |
| 554 | ) |
| 555 | return n + HARDENED_OFFSET |
File History
1 commit
sha256:2a703f78341332ef0beb9856d2267de6aec89b3883c31519b6900b667d026e62
chore: delete muse/prose domain — hallucinated, never existed
Sonnet 4.6
minor
⚠
7 days ago