Skip to content

Feature: Signature verification for Solana and Ethereum messages #19

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Apr 18, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
raise new BadSignatureError exception instead of boolean check for ve…
…rify_signature()
  • Loading branch information
MHHukiewitz committed Apr 18, 2023
commit c8ff9fa59b272770629c75155389262e8859e3b8
16 changes: 9 additions & 7 deletions src/aleph/sdk/chains/ethereum.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
from eth_account import Account
from eth_account.messages import encode_defunct
from eth_account.signers.local import LocalAccount
from eth_keys.exceptions import BadSignature as EthBadSignatureError

from ..exceptions import BadSignatureError
from .common import (
BaseAccount,
get_fallback_private_key,
Expand Down Expand Up @@ -47,13 +49,15 @@ def verify_signature(
signature: Union[bytes, str],
public_key: Union[bytes, str],
message: Union[bytes, str],
) -> bool:
):
"""
Verifies a signature.
Args:
signature: The signature to verify. Can be a hex encoded string or bytes.
public_key: The sender's public key to use for verification. Can be a checksummed, hex encoded string or bytes.
message: The message to verify. Can be an utf-8 string or bytes.
Raises:
BadSignatureError: If the signature is invalid.
"""
if isinstance(signature, str):
if signature.startswith("0x"):
Expand All @@ -71,9 +75,7 @@ def verify_signature(
message_hash = encode_defunct(text=message)
try:
address = Account.recover_message(message_hash, signature=signature)
if address == public_key:
return True
else:
return False
except Exception:
return False
if address != public_key:
raise BadSignatureError
except (EthBadSignatureError, BadSignatureError) as e:
raise BadSignatureError from e
12 changes: 7 additions & 5 deletions src/aleph/sdk/chains/sol.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@
from typing import Dict, Optional, Union

import base58
from nacl.exceptions import BadSignatureError
from nacl.exceptions import BadSignatureError as NaclBadSignatureError
from nacl.public import PrivateKey, SealedBox
from nacl.signing import SigningKey, VerifyKey

from ..conf import settings
from ..exceptions import BadSignatureError
from .common import BaseAccount, get_verification_buffer


Expand Down Expand Up @@ -88,13 +89,15 @@ def verify_signature(
signature: Union[bytes, str],
public_key: Union[bytes, str],
message: Union[bytes, str],
) -> bool:
):
"""
Verifies a signature.
Args:
signature: The signature to verify. Can be a base58 encoded string or bytes.
public_key: The public key to use for verification. Can be a base58 encoded string or bytes.
message: The message to verify. Can be an utf-8 string or bytes.
Raises:
BadSignatureError: If the signature is invalid.
"""
if isinstance(signature, str):
signature = base58.b58decode(signature)
Expand All @@ -104,6 +107,5 @@ def verify_signature(
public_key = base58.b58decode(public_key)
try:
VerifyKey(public_key).verify(message, signature)
return True
except BadSignatureError:
return False
except NaclBadSignatureError as e:
raise BadSignatureError from e
8 changes: 8 additions & 0 deletions src/aleph/sdk/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,11 @@ class InvalidMessageError(BroadcastError):
"""

pass


class BadSignatureError(Exception):
"""
The signature of a message is invalid.
"""

pass
25 changes: 24 additions & 1 deletion tests/unit/test_chain_ethereum.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

from aleph.sdk.chains.common import get_verification_buffer
from aleph.sdk.chains.ethereum import get_fallback_account, verify_signature
from aleph.sdk.exceptions import BadSignatureError


@dataclass
Expand Down Expand Up @@ -58,11 +59,33 @@ async def test_verify_signature(ethereum_account):
await account.sign_message(message)
assert message["signature"]

assert verify_signature(
verify_signature(
message["signature"], message["sender"], get_verification_buffer(message)
)


@pytest.mark.asyncio
async def test_verify_signature_with_forged_signature(ethereum_account):
account = ethereum_account

message = asdict(
Message(
"ETH",
account.get_address(),
"POST",
"SomeHash",
)
)
await account.sign_message(message)
assert message["signature"]

forged_signature = "0x" + "0" * 130
with pytest.raises(BadSignatureError):
verify_signature(
forged_signature, message["sender"], get_verification_buffer(message)
)


@pytest.mark.asyncio
async def test_decrypt_secp256k1(ethereum_account):
account = ethereum_account
Expand Down
22 changes: 20 additions & 2 deletions tests/unit/test_chain_solana.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

from aleph.sdk.chains.common import get_verification_buffer
from aleph.sdk.chains.sol import SOLAccount, get_fallback_account, verify_signature
from aleph.sdk.exceptions import BadSignatureError


@dataclass
Expand Down Expand Up @@ -87,6 +88,23 @@ async def test_verify_signature(solana_account):
raw_signature = json.loads(message["signature"])["signature"]
assert type(raw_signature) == str

assert verify_signature(
raw_signature, message["sender"], get_verification_buffer(message)
verify_signature(raw_signature, message["sender"], get_verification_buffer(message))


@pytest.mark.asyncio
async def test_verify_signature_with_forged_signature(solana_account):
message = asdict(
Message(
"SOL",
solana_account.get_address(),
"POST",
"SomeHash",
)
)
await solana_account.sign_message(message)
assert message["signature"]
# create forged 64 bit signature from random bytes
forged = base58.b58encode(bytes(64)).decode("utf-8")

with pytest.raises(BadSignatureError):
verify_signature(forged, message["sender"], get_verification_buffer(message))