Skip to content

Commit

Permalink
Fix Safe wallet signature verification (#76)
Browse files Browse the repository at this point in the history
Close #63 

---------

Co-authored-by: Simon Bihel <[email protected]>
  • Loading branch information
ljiatu and sbihel authored Oct 11, 2024
1 parent 32022e0 commit 9741fcf
Show file tree
Hide file tree
Showing 3 changed files with 30 additions and 7 deletions.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "siwe"
version = "4.3.0"
version = "4.4.0"
description = "A Python implementation of Sign-In with Ethereum (EIP-4361)."
license = "MIT OR Apache-2.0"
authors = [
Expand Down
12 changes: 8 additions & 4 deletions siwe/siwe.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
from pydantic_core import core_schema
from typing_extensions import Annotated
from web3 import HTTPProvider, Web3
from web3.exceptions import BadFunctionCallOutput
from web3.exceptions import BadFunctionCallOutput, ContractLogicError

from .parsed import ABNFParsedMessage, RegExpParsedMessage

Expand Down Expand Up @@ -327,7 +327,7 @@ def verify(

try:
address = w3.eth.account.recover_message(message, signature=signature)
except ValueError:
except (ValueError, IndexError):
address = None
except eth_utils.exceptions.ValidationError:
raise InvalidSignature from None
Expand Down Expand Up @@ -355,7 +355,11 @@ def check_contract_wallet_signature(
contract = w3.eth.contract(address=address, abi=EIP1271_CONTRACT_ABI)
hash_ = _hash_eip191_message(message)
try:
response = contract.caller.isValidSignature(hash_, bytes.fromhex(signature[2:]))
# For message hashes stored on-chain for Safe wallets, the signatures
# are always "0x" and should be passed in as-is.
response = contract.caller.isValidSignature(
hash_, signature if signature == "0x" else bytes.fromhex(signature[2:])
)
return response.hex() == EIP1271_MAGICVALUE
except BadFunctionCallOutput:
except (BadFunctionCallOutput, ContractLogicError):
return False
23 changes: 21 additions & 2 deletions tests/test_siwe.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,20 @@
with open(BASE_TESTS + "eip1271.json", "r") as f:
verification_eip1271 = decamelize(json.load(fp=f))

endpoint_uri = "https://cloudflare-eth.com"
try:
endpoint_uri = os.environ["WEB3_PROVIDER_URI"]
uri = os.environ["WEB3_PROVIDER_URI"]
if uri != "":
endpoint_uri = uri
except KeyError:
endpoint_uri = "https://cloudflare-eth.com"
pass
sepolia_endpoint_uri = "https://rpc.sepolia.org"
try:
uri = os.environ["WEB3_PROVIDER_URI_SEPOLIA"]
if uri != "":
sepolia_endpoint_uri = uri
except KeyError:
pass


class TestMessageParsing:
Expand Down Expand Up @@ -94,6 +104,15 @@ def test_eip1271_message(self, test_name, test):
siwe_message = SiweMessage.from_message(message=test["message"])
siwe_message.verify(test["signature"], provider=provider)

def test_safe_wallet_message(self):
message = "localhost:3000 wants you to sign in with your Ethereum account:\n0x54D97AEa047838CAC7A9C3e452951647f12a440c\n\nPlease sign in to verify your ownership of this wallet\n\nURI: http://localhost:3000\nVersion: 1\nChain ID: 11155111\nNonce: gDj8rv7VVxN\nIssued At: 2024-10-10T08:34:03.152Z\nExpiration Time: 2024-10-13T08:34:03.249112Z"
signature = "0x"
# Use a Sepolia RPC node since the signature is generated on Sepolia testnet
# instead of mainnet like other EIP-1271 tests.
provider = HTTPProvider(endpoint_uri=sepolia_endpoint_uri)
siwe_message = SiweMessage.from_message(message=message)
siwe_message.verify(signature, provider=provider)

@pytest.mark.parametrize(
"provider", [HTTPProvider(endpoint_uri=endpoint_uri), None]
)
Expand Down

0 comments on commit 9741fcf

Please sign in to comment.