From 580e591989f0bab4e65e0240227a7223850cc29b Mon Sep 17 00:00:00 2001 From: philogicae Date: Wed, 9 Oct 2024 19:15:59 +0300 Subject: [PATCH 1/7] Missing chain field on auth --- src/aleph/sdk/client/vm_client.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/aleph/sdk/client/vm_client.py b/src/aleph/sdk/client/vm_client.py index 18d280cc..5e7c776e 100644 --- a/src/aleph/sdk/client/vm_client.py +++ b/src/aleph/sdk/client/vm_client.py @@ -5,10 +5,11 @@ from urllib.parse import urlparse import aiohttp -from aleph_message.models import ItemHash +from aleph_message.models import Chain, ItemHash from eth_account.messages import encode_defunct from jwcrypto import jwk +from aleph.sdk.chains.solana import SOLAccount from aleph.sdk.types import Account from aleph.sdk.utils import ( create_vm_control_payload, @@ -36,11 +37,13 @@ def __init__( self.account = account self.ephemeral_key = jwk.JWK.generate(kty="EC", crv="P-256") self.node_url = node_url.rstrip("/") - self.pubkey_payload = self._generate_pubkey_payload() + self.pubkey_payload = self._generate_pubkey_payload( + Chain.SOL if isinstance(account, SOLAccount) else Chain.ETH + ) self.pubkey_signature_header = "" self.session = session or aiohttp.ClientSession() - def _generate_pubkey_payload(self) -> Dict[str, Any]: + def _generate_pubkey_payload(self, chain: Chain = Chain.ETH) -> Dict[str, Any]: return { "pubkey": json.loads(self.ephemeral_key.export_public()), "alg": "ECDSA", @@ -50,6 +53,7 @@ def _generate_pubkey_payload(self) -> Dict[str, Any]: datetime.datetime.utcnow() + datetime.timedelta(days=1) ).isoformat() + "Z", + "chain": chain.value, } async def _generate_pubkey_signature_header(self) -> str: From ac45647eb151eecafafdb721207a151ce7f8d4ee Mon Sep 17 00:00:00 2001 From: Olivier Le Thanh Duong Date: Thu, 10 Oct 2024 18:47:37 +0200 Subject: [PATCH 2/7] Fix Signature of Solana operation for CRN --- src/aleph/sdk/client/vm_client.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/aleph/sdk/client/vm_client.py b/src/aleph/sdk/client/vm_client.py index 5e7c776e..83b00dc9 100644 --- a/src/aleph/sdk/client/vm_client.py +++ b/src/aleph/sdk/client/vm_client.py @@ -58,8 +58,11 @@ def _generate_pubkey_payload(self, chain: Chain = Chain.ETH) -> Dict[str, Any]: async def _generate_pubkey_signature_header(self) -> str: pubkey_payload = json.dumps(self.pubkey_payload).encode("utf-8").hex() - signable_message = encode_defunct(hexstr=pubkey_payload) - buffer_to_sign = signable_message.body + if isinstance(self.account, SOLAccount): + buffer_to_sign = bytes(pubkey_payload, encoding="utf-8") + else: + signable_message = encode_defunct(hexstr=pubkey_payload) + buffer_to_sign = signable_message.body signed_message = await self.account.sign_raw(buffer_to_sign) pubkey_signature = to_0x_hex(signed_message) From b485675a1c85bc5ee59044ddb03ab692272815cd Mon Sep 17 00:00:00 2001 From: philogicae Date: Fri, 11 Oct 2024 04:57:53 +0300 Subject: [PATCH 3/7] Add export_private_key func for accounts --- src/aleph/sdk/chains/ethereum.py | 5 +++++ src/aleph/sdk/chains/solana.py | 6 ++++++ src/aleph/sdk/types.py | 2 ++ 3 files changed, 13 insertions(+) diff --git a/src/aleph/sdk/chains/ethereum.py b/src/aleph/sdk/chains/ethereum.py index 32f459b7..ab93df56 100644 --- a/src/aleph/sdk/chains/ethereum.py +++ b/src/aleph/sdk/chains/ethereum.py @@ -1,4 +1,5 @@ import asyncio +import base64 from decimal import Decimal from pathlib import Path from typing import Awaitable, Optional, Union @@ -61,6 +62,10 @@ def from_mnemonic(mnemonic: str, chain: Optional[Chain] = None) -> "ETHAccount": private_key=Account.from_mnemonic(mnemonic=mnemonic).key, chain=chain ) + def export_private_key(self) -> str: + """Export the private key using standard format.""" + return f"0x{base64.b16encode(self.private_key).decode().lower()}" + def get_address(self) -> str: return self._account.address diff --git a/src/aleph/sdk/chains/solana.py b/src/aleph/sdk/chains/solana.py index a9352489..920ca8a0 100644 --- a/src/aleph/sdk/chains/solana.py +++ b/src/aleph/sdk/chains/solana.py @@ -43,6 +43,12 @@ async def sign_raw(self, buffer: bytes) -> bytes: sig = self._signing_key.sign(buffer) return sig.signature + def export_private_key(self) -> str: + """Export the private key using Phantom format.""" + return base58.b58encode( + self.private_key + self._signing_key.verify_key.encode() + ).decode() + def get_address(self) -> str: return encode(self._signing_key.verify_key) diff --git a/src/aleph/sdk/types.py b/src/aleph/sdk/types.py index 081a3465..5752ed72 100644 --- a/src/aleph/sdk/types.py +++ b/src/aleph/sdk/types.py @@ -39,6 +39,8 @@ def __init__(self, private_key: bytes): ... async def sign_raw(self, buffer: bytes) -> bytes: ... + def export_private_key(self) -> str: ... + GenericMessage = TypeVar("GenericMessage", bound=AlephMessage) From 22eda926d5301a3a10ceec550117108ac711c028 Mon Sep 17 00:00:00 2001 From: philogicae Date: Fri, 11 Oct 2024 04:58:51 +0300 Subject: [PATCH 4/7] Improve _load_account --- src/aleph/sdk/account.py | 51 ++++++++++++++++++---------------------- 1 file changed, 23 insertions(+), 28 deletions(-) diff --git a/src/aleph/sdk/account.py b/src/aleph/sdk/account.py index 8c067283..d3ef008d 100644 --- a/src/aleph/sdk/account.py +++ b/src/aleph/sdk/account.py @@ -21,8 +21,8 @@ def load_chain_account_type(chain: Chain) -> Type[AccountFromPrivateKey]: chain_account_map: Dict[Chain, Type[AccountFromPrivateKey]] = { Chain.ETH: ETHAccount, Chain.AVAX: ETHAccount, - Chain.SOL: SOLAccount, Chain.BASE: ETHAccount, + Chain.SOL: SOLAccount, } return chain_account_map.get(chain) or ETHAccount @@ -43,34 +43,29 @@ def _load_account( private_key_path: Optional[Path] = None, account_type: Optional[Type[AccountFromPrivateKey]] = None, ) -> AccountFromPrivateKey: - """Load private key from a string or a file. takes the string argument in priority""" - if private_key_str or (private_key_path and private_key_path.is_file()): - if account_type: - if private_key_path and private_key_path.is_file(): - return account_from_file(private_key_path, account_type) - elif private_key_str: - return account_from_hex_string(private_key_str, account_type) - else: - raise ValueError("Any private key specified") + """Load an account from a private key string or file, or from the configuration file.""" + + # Loads configuration if no account_type is specified + if not account_type: + config = load_main_configuration(settings.CONFIG_FILE) + if config and hasattr(config, "chain"): + account_type = load_chain_account_type(config.chain) + logger.debug( + f"Detected {config.chain} account for path {settings.CONFIG_FILE}" + ) else: - main_configuration = load_main_configuration(settings.CONFIG_FILE) - if main_configuration: - account_type = load_chain_account_type(main_configuration.chain) - logger.debug( - f"Detected {main_configuration.chain} account for path {settings.CONFIG_FILE}" - ) - else: - account_type = ETHAccount # Defaults to ETHAccount - logger.warning( - f"No main configuration data found in {settings.CONFIG_FILE}, defaulting to {account_type.__name__}" - ) - if private_key_path and private_key_path.is_file(): - return account_from_file(private_key_path, account_type) - elif private_key_str: - return account_from_hex_string(private_key_str, account_type) - else: - raise ValueError("Any private key specified") + account_type = ETHAccount # Defaults to ETHAccount + logger.warning( + f"No main configuration data found in {settings.CONFIG_FILE}, defaulting to {account_type.__name__}" + ) + # Loads private key from a string + if private_key_str: + return account_from_hex_string(private_key_str, account_type) + # Loads private key from a file + elif private_key_path and private_key_path.is_file(): + return account_from_file(private_key_path, account_type) + # For ledger keys elif settings.REMOTE_CRYPTO_HOST: logger.debug("Using remote account") loop = asyncio.get_event_loop() @@ -80,8 +75,8 @@ def _load_account( unix_socket=settings.REMOTE_CRYPTO_UNIX_SOCKET, ) ) + # Fallback: config.path if set, else generate a new private key else: - account_type = ETHAccount # Defaults to ETHAccount new_private_key = get_fallback_private_key() account = account_type(private_key=new_private_key) logger.info( From e1be4fe5f851502fa881ae9189603bdf3e5d8f68 Mon Sep 17 00:00:00 2001 From: philogicae Date: Fri, 11 Oct 2024 16:08:13 +0300 Subject: [PATCH 5/7] Add chain arg to _load_account --- src/aleph/sdk/account.py | 44 +++++++++++++++++++++++++++++++++------- src/aleph/sdk/types.py | 2 ++ 2 files changed, 39 insertions(+), 7 deletions(-) diff --git a/src/aleph/sdk/account.py b/src/aleph/sdk/account.py index d3ef008d..11f621fe 100644 --- a/src/aleph/sdk/account.py +++ b/src/aleph/sdk/account.py @@ -10,6 +10,7 @@ from aleph.sdk.chains.remote import RemoteAccount from aleph.sdk.chains.solana import SOLAccount from aleph.sdk.conf import load_main_configuration, settings +from aleph.sdk.evm_utils import get_chains_with_super_token from aleph.sdk.types import AccountFromPrivateKey logger = logging.getLogger(__name__) @@ -27,21 +28,48 @@ def load_chain_account_type(chain: Chain) -> Type[AccountFromPrivateKey]: return chain_account_map.get(chain) or ETHAccount -def account_from_hex_string(private_key_str: str, account_type: Type[T]) -> T: +def account_from_hex_string( + private_key_str: str, account_type: Optional[Type[T]], chain: Optional[Chain] = None +) -> T: if private_key_str.startswith("0x"): private_key_str = private_key_str[2:] - return account_type(bytes.fromhex(private_key_str)) + if not chain: + if not account_type: + account_type = ETHAccount + return account_type(bytes.fromhex(private_key_str)) -def account_from_file(private_key_path: Path, account_type: Type[T]) -> T: + account_type = load_chain_account_type(chain) + account = account_type(bytes.fromhex(private_key_str)) + if chain in get_chains_with_super_token(): + account.switch_chain(chain) + return account + + +def account_from_file( + private_key_path: Path, + account_type: Optional[Type[T]], + chain: Optional[Chain] = None, +) -> T: private_key = private_key_path.read_bytes() - return account_type(private_key) + + if not chain: + if not account_type: + account_type = ETHAccount + return account_type(private_key) + + account_type = load_chain_account_type(chain) + account = account_type(private_key) + if chain in get_chains_with_super_token(): + account.switch_chain(chain) + return account def _load_account( private_key_str: Optional[str] = None, private_key_path: Optional[Path] = None, account_type: Optional[Type[AccountFromPrivateKey]] = None, + chain: Optional[Chain] = None, ) -> AccountFromPrivateKey: """Load an account from a private key string or file, or from the configuration file.""" @@ -61,10 +89,10 @@ def _load_account( # Loads private key from a string if private_key_str: - return account_from_hex_string(private_key_str, account_type) + return account_from_hex_string(private_key_str, account_type, chain) # Loads private key from a file elif private_key_path and private_key_path.is_file(): - return account_from_file(private_key_path, account_type) + return account_from_file(private_key_path, account_type, chain) # For ledger keys elif settings.REMOTE_CRYPTO_HOST: logger.debug("Using remote account") @@ -78,7 +106,9 @@ def _load_account( # Fallback: config.path if set, else generate a new private key else: new_private_key = get_fallback_private_key() - account = account_type(private_key=new_private_key) + account = account_from_hex_string( + bytes.hex(new_private_key), account_type, chain + ) logger.info( f"Generated fallback private key with address {account.get_address()}" ) diff --git a/src/aleph/sdk/types.py b/src/aleph/sdk/types.py index 5752ed72..dab90379 100644 --- a/src/aleph/sdk/types.py +++ b/src/aleph/sdk/types.py @@ -41,6 +41,8 @@ async def sign_raw(self, buffer: bytes) -> bytes: ... def export_private_key(self) -> str: ... + def switch_chain(self, chain: Optional[str] = None) -> None: ... + GenericMessage = TypeVar("GenericMessage", bound=AlephMessage) From 8b9d4d415a3f73c62b70e461878c6c8bfaccec71 Mon Sep 17 00:00:00 2001 From: philogicae Date: Fri, 11 Oct 2024 16:08:57 +0300 Subject: [PATCH 6/7] Increase default HTTP_REQUEST_TIMEOUT --- src/aleph/sdk/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aleph/sdk/conf.py b/src/aleph/sdk/conf.py index 114652b7..5fe4cd4b 100644 --- a/src/aleph/sdk/conf.py +++ b/src/aleph/sdk/conf.py @@ -41,7 +41,7 @@ class Settings(BaseSettings): REMOTE_CRYPTO_HOST: Optional[str] = None REMOTE_CRYPTO_UNIX_SOCKET: Optional[str] = None ADDRESS_TO_USE: Optional[str] = None - HTTP_REQUEST_TIMEOUT = 10.0 + HTTP_REQUEST_TIMEOUT = 15.0 DEFAULT_CHANNEL: str = "ALEPH-CLOUDSOLUTIONS" DEFAULT_RUNTIME_ID: str = ( From 9ae35fbf0575d414e90cd4215e90585e0bb25c71 Mon Sep 17 00:00:00 2001 From: philogicae Date: Fri, 11 Oct 2024 19:46:27 +0300 Subject: [PATCH 7/7] Typing --- src/aleph/sdk/account.py | 39 ++++++++++++++++++++++----------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/src/aleph/sdk/account.py b/src/aleph/sdk/account.py index 11f621fe..9bfafcd3 100644 --- a/src/aleph/sdk/account.py +++ b/src/aleph/sdk/account.py @@ -17,46 +17,49 @@ T = TypeVar("T", bound=AccountFromPrivateKey) +chain_account_map: Dict[Chain, Type[T]] = { # type: ignore + Chain.ETH: ETHAccount, + Chain.AVAX: ETHAccount, + Chain.BASE: ETHAccount, + Chain.SOL: SOLAccount, +} + def load_chain_account_type(chain: Chain) -> Type[AccountFromPrivateKey]: - chain_account_map: Dict[Chain, Type[AccountFromPrivateKey]] = { - Chain.ETH: ETHAccount, - Chain.AVAX: ETHAccount, - Chain.BASE: ETHAccount, - Chain.SOL: SOLAccount, - } - return chain_account_map.get(chain) or ETHAccount + return chain_account_map.get(chain) or ETHAccount # type: ignore def account_from_hex_string( - private_key_str: str, account_type: Optional[Type[T]], chain: Optional[Chain] = None -) -> T: + private_key_str: str, + account_type: Optional[Type[T]], + chain: Optional[Chain] = None, +) -> AccountFromPrivateKey: if private_key_str.startswith("0x"): private_key_str = private_key_str[2:] if not chain: if not account_type: - account_type = ETHAccount - return account_type(bytes.fromhex(private_key_str)) + account_type = load_chain_account_type(Chain.ETH) # type: ignore + return account_type(bytes.fromhex(private_key_str)) # type: ignore account_type = load_chain_account_type(chain) account = account_type(bytes.fromhex(private_key_str)) if chain in get_chains_with_super_token(): account.switch_chain(chain) - return account + return account # type: ignore def account_from_file( private_key_path: Path, account_type: Optional[Type[T]], chain: Optional[Chain] = None, -) -> T: +) -> AccountFromPrivateKey: private_key = private_key_path.read_bytes() if not chain: if not account_type: - account_type = ETHAccount - return account_type(private_key) + account_type = load_chain_account_type(Chain.ETH) # type: ignore + return account_type(private_key) # type: ignore account_type = load_chain_account_type(chain) account = account_type(private_key) @@ -82,9 +85,11 @@ def _load_account( f"Detected {config.chain} account for path {settings.CONFIG_FILE}" ) else: - account_type = ETHAccount # Defaults to ETHAccount + account_type = account_type = load_chain_account_type( + Chain.ETH + ) # Defaults to ETHAccount logger.warning( - f"No main configuration data found in {settings.CONFIG_FILE}, defaulting to {account_type.__name__}" + f"No main configuration data found in {settings.CONFIG_FILE}, defaulting to {account_type and account_type.__name__}" ) # Loads private key from a string