diff --git a/pyproject.toml b/pyproject.toml index 898e24b66..815b49f21 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,7 +11,7 @@ license = { file = "LICENSE.txt" } authors = [ { name = "Moshe Malawach", email = "moshe.malawach@protonmail.com" }, ] -requires-python = ">=3.10,<3.13" +requires-python = ">=3.12,<3.13" classifiers = [ "Development Status :: 4 - Beta", "Programming Language :: Python", @@ -32,7 +32,7 @@ dependencies = [ "aleph-nuls2==0.1", "aleph-p2p-client @ git+https://github.com/aleph-im/p2p-service-client-python@2c04af39c566217f629fd89505ffc3270fba8676", "aleph-pytezos==3.13.4", - "asyncpg==0.30.0", + "asyncpg==0.30", "base58>=1.0.3", "coincurve==20", "configmanager==1.35.1", diff --git a/src/aleph/chains/ethereum.py b/src/aleph/chains/ethereum.py index 1036166a8..db83aec07 100644 --- a/src/aleph/chains/ethereum.py +++ b/src/aleph/chains/ethereum.py @@ -1,5 +1,4 @@ import asyncio -import functools import importlib.resources import json import logging @@ -8,7 +7,6 @@ from aleph_message.models import Chain from configmanager import Config from eth_account import Account -from eth_account.messages import encode_defunct from hexbytes import HexBytes from web3 import Web3 from web3._utils.events import get_event_data @@ -17,21 +15,20 @@ from web3.middleware.filter import local_filter_middleware from web3.middleware.geth_poa import geth_poa_middleware -from aleph.chains.common import get_verification_buffer from aleph.db.accessors.chains import get_last_height, upsert_chain_sync_status from aleph.db.accessors.messages import get_unconfirmed_messages from aleph.db.accessors.pending_messages import count_pending_messages from aleph.db.accessors.pending_txs import count_pending_txs from aleph.db.models.chains import ChainTxDb from aleph.schemas.chains.tx_context import TxContext -from aleph.schemas.pending_messages import BasePendingMessage from aleph.toolkit.timestamp import utc_now from aleph.types.chain_sync import ChainEventType from aleph.types.db_session import DbSessionFactory from aleph.utils import run_in_executor -from .abc import ChainWriter, Verifier +from .abc import ChainWriter from .chain_data_service import ChainDataService, PendingTxPublisher +from .evm import EVMVerifier from .indexer_reader import AlephIndexerReader LOGGER = logging.getLogger("chains.ethereum") @@ -68,38 +65,8 @@ def get_logs_query(web3: Web3, contract, start_height, end_height): ) -class EthereumVerifier(Verifier): - async def verify_signature(self, message: BasePendingMessage) -> bool: - """Verifies a signature of a message, return True if verified, false if not""" - - verification = get_verification_buffer(message) - - message_hash = await run_in_executor( - None, functools.partial(encode_defunct, text=verification.decode("utf-8")) - ) - - verified = False - try: - # we assume the signature is a valid string - address = await run_in_executor( - None, - functools.partial( - Account.recover_message, message_hash, signature=message.signature - ), - ) - if address == message.sender: - verified = True - else: - LOGGER.warning( - "Received bad signature from %s for %s" % (address, message.sender) - ) - return False - - except Exception: - LOGGER.exception("Error processing signature for %s" % message.sender) - verified = False - - return verified +class EthereumVerifier(EVMVerifier): + pass class EthereumConnector(ChainWriter): diff --git a/src/aleph/chains/evm.py b/src/aleph/chains/evm.py new file mode 100644 index 000000000..5ed1d5923 --- /dev/null +++ b/src/aleph/chains/evm.py @@ -0,0 +1,47 @@ +import functools +import logging + +from eth_account import Account +from eth_account.messages import encode_defunct + +from aleph.chains.common import get_verification_buffer +from aleph.schemas.pending_messages import BasePendingMessage +from aleph.utils import run_in_executor + +from .abc import Verifier + +LOGGER = logging.getLogger("chains.evm") + + +class EVMVerifier(Verifier): + async def verify_signature(self, message: BasePendingMessage) -> bool: + """Verifies a signature of a message, return True if verified, false if not""" + + verification = get_verification_buffer(message) + + message_hash = await run_in_executor( + None, functools.partial(encode_defunct, text=verification.decode("utf-8")) + ) + + verified = False + try: + # we assume the signature is a valid string + address = await run_in_executor( + None, + functools.partial( + Account.recover_message, message_hash, signature=message.signature + ), + ) + if address == message.sender: + verified = True + else: + LOGGER.warning( + "Received bad signature from %s for %s" % (address, message.sender) + ) + return False + + except Exception: + LOGGER.exception("Error processing signature for %s" % message.sender) + verified = False + + return verified diff --git a/src/aleph/chains/signature_verifier.py b/src/aleph/chains/signature_verifier.py index 02da09d79..b87921487 100644 --- a/src/aleph/chains/signature_verifier.py +++ b/src/aleph/chains/signature_verifier.py @@ -5,6 +5,7 @@ from aleph.chains.abc import Verifier from aleph.chains.avalanche import AvalancheConnector from aleph.chains.ethereum import EthereumVerifier +from aleph.chains.evm import EVMVerifier from aleph.chains.nuls import NulsConnector from aleph.chains.nuls2 import Nuls2Verifier from aleph.chains.solana import SolanaConnector @@ -19,13 +20,27 @@ class SignatureVerifier: def __init__(self): self.verifiers = { + Chain.ARBITRUM: EVMVerifier(), Chain.AVAX: AvalancheConnector(), + Chain.BLAST: EVMVerifier(), + Chain.BOB: EVMVerifier(), + Chain.CYBER: EVMVerifier(), Chain.DOT: SubstrateConnector(), Chain.ETH: EthereumVerifier(), + Chain.FRAXTAL: EVMVerifier(), + Chain.INK: EVMVerifier(), + Chain.METIS: EVMVerifier(), + Chain.MODE: EVMVerifier(), Chain.NULS: NulsConnector(), Chain.NULS2: Nuls2Verifier(), + Chain.LINEA: EVMVerifier(), + Chain.LISK: EVMVerifier(), + Chain.OPTIMISM: EVMVerifier(), + Chain.POL: EVMVerifier(), Chain.SOL: SolanaConnector(), Chain.TEZOS: TezosVerifier(), + Chain.WORLDCHAIN: EVMVerifier(), + Chain.ZORA: EVMVerifier(), } async def verify_signature(self, message: BasePendingMessage) -> None: diff --git a/tests/chains/test_evm.py b/tests/chains/test_evm.py new file mode 100644 index 000000000..557834145 --- /dev/null +++ b/tests/chains/test_evm.py @@ -0,0 +1,36 @@ +import pytest + +from aleph.chains.evm import EVMVerifier +from aleph.schemas.pending_messages import BasePendingMessage, parse_message + + +@pytest.fixture +def evm_message() -> BasePendingMessage: + return parse_message( + { + "item_hash": "3d1909797f30fa7868d9cc1138128687d1b501de1ab8bbde40caa4e5a03e02bc", + "type": "POST", + "chain": "SOL", + "sender": "0xA07B1214bAe0D5ccAA25449C3149c0aC83658874", + "signature": "0x006f3014418130fc89fb910ebc0a7742ca7575e72487418482a6990a48250977236674cb603fc5bc0caf5f0ce2ddf3680e9b1947ca3d7698cd2b9af3332135191c", + "item_type": "inline", + "item_content": '{"type":"polygon","address":"0xA07B1214bAe0D5ccAA25449C3149c0aC83658874","content":{"body":"This message was posted from the typescript-SDK test suite"},"time":1689163528.372}', + "time": 1689163528.372, + "channel": "TEST", + } + ) + + +@pytest.mark.asyncio +async def test_verify_evm_signature_real(evm_message: BasePendingMessage): + verifier = EVMVerifier() + result = await verifier.verify_signature(evm_message) + assert result is True + + +@pytest.mark.asyncio +async def test_verify_bad_evm_signature(evm_message: BasePendingMessage): + verifier = EVMVerifier() + evm_message.signature = "baba" + result = await verifier.verify_signature(evm_message) + assert result is False