Skip to content

Commit 9741fcf

Browse files
ljiatusbihel
andauthored
Fix Safe wallet signature verification (#76)
Close #63 --------- Co-authored-by: Simon Bihel <simon.bihel@spruceid.com>
1 parent 32022e0 commit 9741fcf

3 files changed

Lines changed: 30 additions & 7 deletions

File tree

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "siwe"
3-
version = "4.3.0"
3+
version = "4.4.0"
44
description = "A Python implementation of Sign-In with Ethereum (EIP-4361)."
55
license = "MIT OR Apache-2.0"
66
authors = [

siwe/siwe.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
from pydantic_core import core_schema
2222
from typing_extensions import Annotated
2323
from web3 import HTTPProvider, Web3
24-
from web3.exceptions import BadFunctionCallOutput
24+
from web3.exceptions import BadFunctionCallOutput, ContractLogicError
2525

2626
from .parsed import ABNFParsedMessage, RegExpParsedMessage
2727

@@ -327,7 +327,7 @@ def verify(
327327

328328
try:
329329
address = w3.eth.account.recover_message(message, signature=signature)
330-
except ValueError:
330+
except (ValueError, IndexError):
331331
address = None
332332
except eth_utils.exceptions.ValidationError:
333333
raise InvalidSignature from None
@@ -355,7 +355,11 @@ def check_contract_wallet_signature(
355355
contract = w3.eth.contract(address=address, abi=EIP1271_CONTRACT_ABI)
356356
hash_ = _hash_eip191_message(message)
357357
try:
358-
response = contract.caller.isValidSignature(hash_, bytes.fromhex(signature[2:]))
358+
# For message hashes stored on-chain for Safe wallets, the signatures
359+
# are always "0x" and should be passed in as-is.
360+
response = contract.caller.isValidSignature(
361+
hash_, signature if signature == "0x" else bytes.fromhex(signature[2:])
362+
)
359363
return response.hex() == EIP1271_MAGICVALUE
360-
except BadFunctionCallOutput:
364+
except (BadFunctionCallOutput, ContractLogicError):
361365
return False

tests/test_siwe.py

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,20 @@
2323
with open(BASE_TESTS + "eip1271.json", "r") as f:
2424
verification_eip1271 = decamelize(json.load(fp=f))
2525

26+
endpoint_uri = "https://cloudflare-eth.com"
2627
try:
27-
endpoint_uri = os.environ["WEB3_PROVIDER_URI"]
28+
uri = os.environ["WEB3_PROVIDER_URI"]
29+
if uri != "":
30+
endpoint_uri = uri
2831
except KeyError:
29-
endpoint_uri = "https://cloudflare-eth.com"
32+
pass
33+
sepolia_endpoint_uri = "https://rpc.sepolia.org"
34+
try:
35+
uri = os.environ["WEB3_PROVIDER_URI_SEPOLIA"]
36+
if uri != "":
37+
sepolia_endpoint_uri = uri
38+
except KeyError:
39+
pass
3040

3141

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

107+
def test_safe_wallet_message(self):
108+
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"
109+
signature = "0x"
110+
# Use a Sepolia RPC node since the signature is generated on Sepolia testnet
111+
# instead of mainnet like other EIP-1271 tests.
112+
provider = HTTPProvider(endpoint_uri=sepolia_endpoint_uri)
113+
siwe_message = SiweMessage.from_message(message=message)
114+
siwe_message.verify(signature, provider=provider)
115+
97116
@pytest.mark.parametrize(
98117
"provider", [HTTPProvider(endpoint_uri=endpoint_uri), None]
99118
)

0 commit comments

Comments
 (0)