diff --git a/earthsnake/identity.py b/earthsnake/identity.py index a1cbef3..eff8a09 100644 --- a/earthsnake/identity.py +++ b/earthsnake/identity.py @@ -4,7 +4,7 @@ import re from typing import Optional import bip39 -from ed25519 import SigningKey, VerifyingKey, create_keypair +from ed25519 import BadSignatureError, SigningKey, VerifyingKey, create_keypair from .base32 import base32_bytes_to_string, base32_string_to_bytes from .exc import ValidationError @@ -23,8 +23,8 @@ class Identity: _KEY_PATTERN = f'b[{B32_CHAR}]{{52}}' _NAME_PATTERN = f'[{ALPHA_LOWER}][{ALPHA_LOWER_OR_DIGIT}]{{3}}' - _PATTERN = f'^@{_NAME_PATTERN}\\.{_KEY_PATTERN}$' _SECRET_PATTERN = f'^{_KEY_PATTERN}$' + PATTERN = f'^@{_NAME_PATTERN}\\.{_KEY_PATTERN}$' def __init__( self, @@ -143,3 +143,23 @@ class Identity: mnemonic = bip39.encode_bytes(seed) return f'{self.name} {mnemonic}' + + def sign(self, data: str) -> str: + """Sign data""" + + if not self.sign_key: + raise TypeError('This identity doesn’t have a signing key') + + return base32_bytes_to_string(self.sign_key.sign(data.encode('utf-8'))) + + def verify(self, data: str, signature: str) -> bool: + """Verify if data is signed by us""" + + signature_bytes = base32_string_to_bytes(signature) + + try: + self.verify_key.verify(signature_bytes, data.encode('utf-8')) + except BadSignatureError: + return False + + return True diff --git a/tests/test_identity.py b/tests/test_identity.py index 3e51953..e6dfd64 100644 --- a/tests/test_identity.py +++ b/tests/test_identity.py @@ -199,3 +199,56 @@ def test_eq_other(identity: Identity) -> None: with pytest.raises(TypeError): assert identity == 1 + + +def test_sign_nokey() -> None: + """Test if Identity.sign() fails if the identity doesn’t have a signing key available""" + + identity_str = '@test.bcz76z52y5dlpohtkmpuj3jsdcvfmebzpcgfmtmhu4u7hlexzreya' + identity = Identity.from_address(identity_str) + + with pytest.raises(TypeError): + identity.sign('test data') + + +@pytest.mark.id_key_seed(TEST_SEED) +@pytest.mark.id_name('test') +def test_sign(identity: Identity) -> None: + """Test if Identity.sign() works as expected""" + + assert ( + identity.sign('test data') + == 'b6gyis42cvdfhsp7xx3jb4773ebbqoq4zhdo4pyeitskefrfkkzxwdkwjkjpq2oyglpngx4tpzzezeedp7eb' + 'x4i3vkpq7wj6odjinacy' + ) + + +@pytest.mark.id_key_seed(TEST_SEED) +@pytest.mark.id_name('test') +def test_verify(identity: Identity) -> None: + """Test if Identity.verify works as expected""" + + assert ( + identity.verify( + 'test data', + 'b6gyis42cvdfhsp7xx3jb4773ebbqoq4zhdo4pyeitskefrfkkzxwdkwjkjpq2oyglpngx4tpzzezeedp7eb' + 'x4i3vkpq7wj6odjinacy', + ) + is True + ) + assert ( + identity.verify( + 'test date', + 'b6gyis42cvdfhsp7xx3jb4773ebbqoq4zhdo4pyeitskefrfkkzxwdkwjkjpq2oyglpngx4tpzzezeedp7eb' + 'x4i3vkpq7wj6odjinacy', + ) + is False + ) + assert ( + identity.verify( + 'test data', + 'b6gyis42cvdfhsp7xx3jb4773ebbqoq4zhdo4pyeitskefrfkkzxwdkwjkjpq2oyglpngx4tpzzezeedp7eb' + 'x4i3vkpq7wj6odjinacq', + ) + is False + )