web3.py — Python-клиент для узлов Ethereum; подпись сообщений и транзакций делегирована пакету eth-account (0.14.0 вместе с web3 8.0.0), который, в свою очередь, использует eth-keys для арифметики на кривой. Большая часть кода подписи импортирует eth_account напрямую, как и пример.
Account.sign_message(encode_defunct(text=...)) реализует personal_sign: префикс EIP-191 с длиной в байтах в десятичной записи, keccak-256, nonce по RFC 6979 (совпал с независимой эталонной реализацией подписи) и s в нижней половине диапазона; результат возвращается в виде 65 байт, которые заканчиваются значением v = 27 или 28. Про encode_defunct часто забывают: если подписать сырые байты через unsafe_sign_hash, префикс пропускается, и именно из-за этого подпись нельзя использовать как доказательство входа в других системах.
Account.recover_message() восстановил подписанта и по зеркальной подписи с high-S, и при v = 0/1, то есть принимает больше форм, чем ethers — если подписи хранятся или из них удаляются дубликаты, нормализуйте s сами. Компактную 64-байтовую форму EIP-2098 метод отклонил с ошибкой Unexpected recoverable signature length: Expected 65, but got 64 bytes; перед восстановлением преобразуйте её в полную форму.
Для типизированных данных используйте encode_typed_data (EIP-712), а не хешируйте JSON самостоятельно; что именно кошельку предлагают подписать, проверяйте в инспекторе EIP-712. Для кошельков на смарт-контрактах вместо recover_message нужен вызов контракта по ERC-1271; сообщения для входа удобнее всего отлаживать с помощью средства проверки SIWE.
Пример подписывает сообщение для входа и сравнивает восстановленный адрес — именно таким сравнением следует заменять любую побайтовую проверку подписей.