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 typing import Union

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: Union[str, Language] = Language.ENGLISH) -> None: if isinstance(language, Language): language = language.value else: if language not in set(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