Source code for stellar_sdk.sep.mnemonic

"""
SEP: 0005
Title: Key Derivation Methods for Stellar Accounts
"""

import hashlib
import hmac
import os
import struct
from enum import Enum, unique

from mnemonic import Mnemonic
from mnemonic.mnemonic import PBKDF2_ROUNDS


[docs] @unique class Language(Enum): """The type of language supported by the mnemonic.""" JAPANESE = "japanese" FRENCH = "french" ENGLISH = "english" SPANISH = "spanish" ITALIAN = "italian" KOREAN = "korean" CHINESE_SIMPLIFIED = "chinese_simplified" CHINESE_TRADITIONAL = "chinese_traditional"
[docs] class StellarMnemonic(Mnemonic): """Please use :func:`stellar_sdk.keypair.Keypair.generate_mnemonic_phrase` and :func:`stellar_sdk.keypair.Keypair.from_mnemonic_phrase` """ STELLAR_ACCOUNT_PATH_FORMAT = "m/44'/148'/%d'" FIRST_HARDENED_INDEX = 0x80000000 SEED_MODIFIER = b"ed25519 seed" def __init__(self, language: str | Language = Language.ENGLISH) -> None: if isinstance(language, Language): language = language.value else: if language not in {item.value for item in Language}: raise ValueError("This language is not supported.") super().__init__(language)
[docs] def to_seed(self, mnemonic: str, passphrase: str = "", index: int = 0) -> bytes: # type: ignore[override] """Derive an ED25519 key from a mnemonic.""" bip39_seed = self.to_bip39_seed(mnemonic=mnemonic, passphrase=passphrase) return self.derive(bip39_seed[:64], index)
[docs] def to_bip39_seed(self, mnemonic: str, passphrase: str = "") -> bytes: """Derive a BIP-39 key from a mnemonic.""" if not self.check(mnemonic): raise ValueError( "Invalid mnemonic, please check if the mnemonic is correct, " "or if the language is set correctly." ) mnemonic = self.normalize_string(mnemonic) passphrase = self.normalize_string(passphrase) passphrase = "mnemonic" + passphrase mnemonic_bytes = mnemonic.encode("utf-8") passphrase_bytes = passphrase.encode("utf-8") stretched = hashlib.pbkdf2_hmac( "sha512", mnemonic_bytes, passphrase_bytes, PBKDF2_ROUNDS ) return stretched
[docs] def generate(self, strength: int = 128) -> str: if strength not in (128, 160, 192, 224, 256): raise ValueError( f"Strength should be one of the following (128, 160, 192, 224, 256), but it is not ({strength})." ) return self.to_mnemonic(os.urandom(strength // 8))
[docs] @staticmethod def derive(seed: bytes, index: int) -> bytes: """Derive an ED25519 key from a BIP-39 seed.""" # References https://github.com/satoshilabs/slips/blob/master/slip-0010.md master_hmac = hmac.new(StellarMnemonic.SEED_MODIFIER, digestmod=hashlib.sha512) master_hmac.update(seed) il = master_hmac.digest()[:32] ir = master_hmac.digest()[32:] path = StellarMnemonic.STELLAR_ACCOUNT_PATH_FORMAT % index for x in path.split("/")[1:]: data = ( struct.pack("x") + il + struct.pack(">I", StellarMnemonic.FIRST_HARDENED_INDEX + int(x[:-1])) ) i = hmac.new(ir, digestmod=hashlib.sha512) i.update(data) il = i.digest()[:32] ir = i.digest()[32:] return il