"""
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
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