Source code for rizemind.authentication.config

from typing import Any

from eth_account import Account
from eth_account.signers.base import BaseAccount
from flwr.common.context import Context
from mnemonic import Mnemonic
from pydantic import BaseModel, Field, field_validator, model_validator

from rizemind.configuration.base_config import BaseConfig
from rizemind.configuration.transform import unflatten
from rizemind.mnemonic.store import MnemonicStore

ACCOUNT_CONFIG_STATE_KEY = "rizemind.account"


[docs] class MnemonicStoreConfig(BaseModel): """A Pydantic model to store mnemonics.""" account_name: str = Field(..., description="account name") passphrase: str = Field(..., description="Pass-phrase that unlocks the keystore")
[docs] class AccountConfig(BaseConfig): """Ethereum account configuration. Accepts **one** of two authentication sources: direct mnemonic string or a keystore reference. For example: 1. Direct mnemonic string ```toml [tool.eth.account] mnemonic = "test test … junk" ``` 2. Keystore reference ```toml [tool.eth.account.mnemonic_store] account_name = "bob" passphrase = "open sesame" ``` Attributes: mnemonic: BIP-39 seed phrase. Leave empty if using mnemonic_store. mnemonic_store: Keystore configuration. default_account_index: The default HD wallet account index to use. """ mnemonic: str | None = Field( default=None, description="BIP-39 seed phrase (leave empty if using mnemonic_store)", ) mnemonic_store: MnemonicStoreConfig | None = None default_account_index: int | None = Field(default=None) @field_validator("mnemonic") @classmethod def _validate_mnemonic(cls, value: str) -> str: """Validate a mnemonic phrase.""" mnemo = Mnemonic("english") if not mnemo.check(value): raise ValueError("Invalid mnemonic phrase") return value @model_validator(mode="after") def _populate_and_sanity_check(self) -> "AccountConfig": """Ensure exactly one variant is supplied. If the keystore path is chosen, decrypt it and populate ``self.mnemonic``. Raises: ValueError: If both or neither of ``mnemonic`` and ``mnemonic_store`` are provided, or if the provided mnemonic does not match the one in the keystore. """ if self.mnemonic_store is not None: store_conf = self.mnemonic_store store = MnemonicStore() mnemonic_stored = store.load( account_name=store_conf.account_name, passphrase=store_conf.passphrase, ) if self.mnemonic is None: self.mnemonic = mnemonic_stored elif self.mnemonic != mnemonic_stored: raise ValueError( "The mnemonic stored in the keystore does not match the mnemonic provided." ) if self.mnemonic is None: raise ValueError( "You must supply either 'mnemonic' **or** 'mnemonic_store', but not both." ) return self
[docs] def get_account(self, i: int | None = None) -> BaseAccount: """Get the i-th HD wallet account. Args: i: The account index. If not supplied, the ``default_account_index`` from the config will be used. Returns: The account. Raises: ValueError: If no account index is provided and no default is configured. """ if i is None: i = self.default_account_index if i is None: raise ValueError( "no default_account_index specified, provide the index as an argument" ) hd_path = f"m/44'/60'/0'/0/{i}" Account.enable_unaudited_hdwallet_features() return Account.from_mnemonic(self.mnemonic, account_path=hd_path)
[docs] @staticmethod def from_context(context: Context) -> "AccountConfig | None": """Get the ``AccountConfig`` from the context if it exists. Args: context: The Flower context. Returns: The config if it exists, otherwise ``None``. """ if ACCOUNT_CONFIG_STATE_KEY in context.state.config_records: records: Any = context.state.config_records[ACCOUNT_CONFIG_STATE_KEY] return AccountConfig(**unflatten(records)) return None