web3.py は Ethereum ノード用の Python クライアントです。メッセージとトランザクションの署名は eth-account パッケージ(web3 8.0.0 では 0.14.0)に任されており、このパッケージは曲線の演算に eth-keys を使います。署名を行うコードの多くは、サンプルと同様に eth_account を直接インポートします。
Account.sign_message(encode_defunct(text=...)) は personal_sign を実装しています。10 進数のバイト長を含む EIP-191 のプレフィックス、keccak-256、RFC 6979 の nonce(独立した参照実装の署名と照合済み)、下半分に収まる s を使い、末尾が v = 27 または 28 の 65 バイトを返します。忘れられがちなのが encode_defunct です。unsafe_sign_hash で生のバイト列に署名するとプレフィックスが付かず、まさにそれが原因で、その署名はほかの場所でログインの証明として使えなくなります。
Account.recover_message() は、s を n − s に置き換えた high-S 署名からも、v = 0/1 の署名からも署名者を復元しました。つまり ethers より多くの形式を受け入れるため、署名を保存したり重複排除したりする場合は、自分で s を正規化してください。64 バイトの EIP-2098 コンパクト形式は Unexpected recoverable signature length: Expected 65, but got 64 bytes で拒否されました。復元の前に展開してください。
型付きデータには、JSON を自分でハッシュするのではなく encode_typed_data(EIP-712)を使ってください。ウォレットに何への署名が求められているかは EIP-712 インスペクターで確認できます。スマートコントラクトウォレットでは、recover_message の代わりにコントラクトへの ERC-1271 呼び出しが必要です。ログインメッセージは SIWE 検証ツールを使うと最も簡単にデバッグできます。
サンプルはログインメッセージに署名し、復元したアドレスを比較します。署名をバイト単位で比較する処理は、すべてこの比較に置き換えるべきです。