diff --git a/README.md b/README.md index 21ac93f..ecc18f8 100644 --- a/README.md +++ b/README.md @@ -24,8 +24,6 @@ - [x] **NIP-04**: Encrypted Direct Message - if `AUTH` enabled: send only to the intended target - [x] **NIP-09**: Event Deletion - - [x] 'e' tags: Delete regular events by event ID - - [x] 'a' tags: Delete addressable events by address (kind:pubkey:d-identifier) - [x] **NIP-11**: Relay Information Document - > **Note**: the endpoint is NOT on the root level of the domain. It also includes a path (eg https://lnbits.link/nostrrelay/) - [ ] **NIP-12**: Generic Tag Queries diff --git a/config.json b/config.json index a837ae0..0f9b8e6 100644 --- a/config.json +++ b/config.json @@ -1,9 +1,8 @@ { "name": "Nostr Relay", - "version": "1.1.0", "short_description": "One click launch your own relay!", "tile": "/nostrrelay/static/image/nostrrelay.png", - "min_lnbits_version": "1.4.0", + "min_lnbits_version": "1.0.0", "contributors": [ { "name": "motorina0", diff --git a/pyproject.toml b/pyproject.toml index 0487c4d..51b273b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "nostrrelay" -version = "1.1.0" +version = "0.0.0" requires-python = ">=3.10,<3.13" description = "LNbits, free and open-source Lightning wallet and accounts system." authors = [{ name = "Alan Bits", email = "alan@lnbits.com" }] @@ -30,6 +30,12 @@ init_typed = true warn_required_dynamic_aliases = true warn_untyped_fields = true +[[tool.mypy.overrides]] +module = [ + "secp256k1.*", +] +ignore_missing_imports = "True" + [tool.pytest.ini_options] log_cli = false testpaths = [ diff --git a/relay/client_connection.py b/relay/client_connection.py index 695b369..77a25b9 100644 --- a/relay/client_connection.py +++ b/relay/client_connection.py @@ -208,65 +208,12 @@ class NostrClientConnection: await self.websocket.send_text(json.dumps(data)) async def _handle_delete_event(self, event: NostrEvent): - # NIP 09 - Handle both regular events (e tags) and parameterized replaceable events (a tags) - - # Get event IDs from 'e' tags (for regular events) - event_ids = [t[1] for t in event.tags if t[0] == "e"] - - # Get event addresses from 'a' tags (for parameterized replaceable events) - event_addresses = [t[1] for t in event.tags if t[0] == "a"] - - ids_to_delete = [] - - # Handle regular event deletions (e tags) - if event_ids: - nostr_filter = NostrFilter(authors=[event.pubkey], ids=event_ids) - events_to_delete = await get_events(self.relay_id, nostr_filter, False) - ids_to_delete.extend( - [e.id for e in events_to_delete if not e.is_delete_event] - ) - - # Handle parameterized replaceable event deletions (a tags) - if event_addresses: - for addr in event_addresses: - # Parse address format: kind:pubkey:d-tag - parts = addr.split(":") - if len(parts) == 3: - kind_str, addr_pubkey, d_tag = parts - try: - kind = int(kind_str) - # Only delete if the address pubkey matches the deletion event author - if addr_pubkey == event.pubkey: - # NOTE: Use "#d" alias, not "d" directly (Pydantic Field alias) - nostr_filter = NostrFilter( - authors=[addr_pubkey], - kinds=[kind], - **{"#d": [d_tag]}, # Use alias to set d field - ) - events_to_delete = await get_events( - self.relay_id, nostr_filter, False - ) - ids_to_delete.extend( - [ - e.id - for e in events_to_delete - if not e.is_delete_event - ] - ) - else: - logger.warning( - f"Deletion request pubkey mismatch: {addr_pubkey} != {event.pubkey}" - ) - except ValueError: - logger.warning(f"Invalid kind in address: {addr}") - else: - logger.warning( - f"Invalid address format (expected kind:pubkey:d-tag): {addr}" - ) - - # Only mark events as deleted if we found specific IDs - if ids_to_delete: - await mark_events_deleted(self.relay_id, NostrFilter(ids=ids_to_delete)) + # NIP 09 + nostr_filter = NostrFilter(authors=[event.pubkey]) + nostr_filter.ids = [t[1] for t in event.tags if t[0] == "e"] + events_to_delete = await get_events(self.relay_id, nostr_filter, False) + ids = [e.id for e in events_to_delete if not e.is_delete_event] + await mark_events_deleted(self.relay_id, NostrFilter(ids=ids)) async def _handle_request( self, subscription_id: str, nostr_filter: NostrFilter diff --git a/relay/event.py b/relay/event.py index 7154ece..44d34ae 100644 --- a/relay/event.py +++ b/relay/event.py @@ -2,8 +2,8 @@ import hashlib import json from enum import Enum -from coincurve import PublicKeyXOnly from pydantic import BaseModel, Field +from secp256k1 import PublicKey class NostrEventType(str, Enum): @@ -82,15 +82,14 @@ class NostrEvent(BaseModel): f"Invalid event id. Expected: '{event_id}' got '{self.id}'" ) try: - pub_key = PublicKeyXOnly(bytes.fromhex(self.pubkey)) + pub_key = PublicKey(bytes.fromhex("02" + self.pubkey), True) except Exception as exc: raise ValueError( f"Invalid public key: '{self.pubkey}' for event '{self.id}'" ) from exc - valid_signature = pub_key.verify( - bytes.fromhex(self.sig), - bytes.fromhex(event_id), + valid_signature = pub_key.schnorr_verify( + bytes.fromhex(event_id), bytes.fromhex(self.sig), None, raw=True ) if not valid_signature: raise ValueError(f"Invalid signature: '{self.sig}' for event '{self.id}'") diff --git a/tests/conftest.py b/tests/conftest.py index c5ee8a9..80fa60c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -27,18 +27,13 @@ def event_loop(): @pytest_asyncio.fixture(scope="session", autouse=True) async def migrate_db(): + print("#### 999") db = Database("ext_nostrrelay") - await db.execute("DROP TABLE IF EXISTS nostrrelay.events;") - await db.execute("DROP TABLE IF EXISTS nostrrelay.relays;") - await db.execute("DROP TABLE IF EXISTS nostrrelay.event_tags;") - await db.execute("DROP TABLE IF EXISTS nostrrelay.accounts;") - - # check if exists else skip migrations for key, migrate in inspect.getmembers(migrations, inspect.isfunction): + print("### 1000") logger.info(f"Running migration '{key}'.") await migrate(db) - - yield db + return db @pytest.fixture(scope="session") diff --git a/tests/test_events.py b/tests/test_events.py index f2cbe28..669fe31 100644 --- a/tests/test_events.py +++ b/tests/test_events.py @@ -20,7 +20,7 @@ def test_valid_event_id_and_signature(valid_events: list[EventFixture]): try: f.data.check_signature() except Exception as e: - logger.error(f"Invalid 'id' of 'signature' for fixture: '{f.name}'") + logger.error(f"Invalid 'id' ot 'signature' for fixture: '{f.name}'") raise e diff --git a/uv.lock b/uv.lock index 16775d3..23213d3 100644 --- a/uv.lock +++ b/uv.lock @@ -847,8 +847,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7f/91/ae2eb6b7979e2f9b035a9f612cf70f1bf54aad4e1d125129bef1eae96f19/greenlet-3.2.4-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c2ca18a03a8cfb5b25bc1cbe20f3d9a4c80d8c3b13ba3df49ac3961af0b1018d", size = 584358, upload-time = "2025-08-07T13:18:23.708Z" }, { url = "https://files.pythonhosted.org/packages/f7/85/433de0c9c0252b22b16d413c9407e6cb3b41df7389afc366ca204dbc1393/greenlet-3.2.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9fe0a28a7b952a21e2c062cd5756d34354117796c6d9215a87f55e38d15402c5", size = 1113550, upload-time = "2025-08-07T13:42:37.467Z" }, { url = "https://files.pythonhosted.org/packages/a1/8d/88f3ebd2bc96bf7747093696f4335a0a8a4c5acfcf1b757717c0d2474ba3/greenlet-3.2.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8854167e06950ca75b898b104b63cc646573aa5fef1353d4508ecdd1ee76254f", size = 1137126, upload-time = "2025-08-07T13:18:20.239Z" }, - { url = "https://files.pythonhosted.org/packages/f1/29/74242b7d72385e29bcc5563fba67dad94943d7cd03552bac320d597f29b2/greenlet-3.2.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f47617f698838ba98f4ff4189aef02e7343952df3a615f847bb575c3feb177a7", size = 1544904, upload-time = "2025-11-04T12:42:04.763Z" }, - { url = "https://files.pythonhosted.org/packages/c8/e2/1572b8eeab0f77df5f6729d6ab6b141e4a84ee8eb9bc8c1e7918f94eda6d/greenlet-3.2.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:af41be48a4f60429d5cad9d22175217805098a9ef7c40bfef44f7669fb9d74d8", size = 1611228, upload-time = "2025-11-04T12:42:08.423Z" }, { url = "https://files.pythonhosted.org/packages/d6/6f/b60b0291d9623c496638c582297ead61f43c4b72eef5e9c926ef4565ec13/greenlet-3.2.4-cp310-cp310-win_amd64.whl", hash = "sha256:73f49b5368b5359d04e18d15828eecc1806033db5233397748f4ca813ff1056c", size = 298654, upload-time = "2025-08-07T13:50:00.469Z" }, { url = "https://files.pythonhosted.org/packages/a4/de/f28ced0a67749cac23fecb02b694f6473f47686dff6afaa211d186e2ef9c/greenlet-3.2.4-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:96378df1de302bc38e99c3a9aa311967b7dc80ced1dcc6f171e99842987882a2", size = 272305, upload-time = "2025-08-07T13:15:41.288Z" }, { url = "https://files.pythonhosted.org/packages/09/16/2c3792cba130000bf2a31c5272999113f4764fd9d874fb257ff588ac779a/greenlet-3.2.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1ee8fae0519a337f2329cb78bd7a8e128ec0f881073d43f023c7b8d4831d5246", size = 632472, upload-time = "2025-08-07T13:42:55.044Z" }, @@ -858,8 +856,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1f/8e/abdd3f14d735b2929290a018ecf133c901be4874b858dd1c604b9319f064/greenlet-3.2.4-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2523e5246274f54fdadbce8494458a2ebdcdbc7b802318466ac5606d3cded1f8", size = 587684, upload-time = "2025-08-07T13:18:25.164Z" }, { url = "https://files.pythonhosted.org/packages/5d/65/deb2a69c3e5996439b0176f6651e0052542bb6c8f8ec2e3fba97c9768805/greenlet-3.2.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1987de92fec508535687fb807a5cea1560f6196285a4cde35c100b8cd632cc52", size = 1116647, upload-time = "2025-08-07T13:42:38.655Z" }, { url = "https://files.pythonhosted.org/packages/3f/cc/b07000438a29ac5cfb2194bfc128151d52f333cee74dd7dfe3fb733fc16c/greenlet-3.2.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:55e9c5affaa6775e2c6b67659f3a71684de4c549b3dd9afca3bc773533d284fa", size = 1142073, upload-time = "2025-08-07T13:18:21.737Z" }, - { url = "https://files.pythonhosted.org/packages/67/24/28a5b2fa42d12b3d7e5614145f0bd89714c34c08be6aabe39c14dd52db34/greenlet-3.2.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c9c6de1940a7d828635fbd254d69db79e54619f165ee7ce32fda763a9cb6a58c", size = 1548385, upload-time = "2025-11-04T12:42:11.067Z" }, - { url = "https://files.pythonhosted.org/packages/6a/05/03f2f0bdd0b0ff9a4f7b99333d57b53a7709c27723ec8123056b084e69cd/greenlet-3.2.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:03c5136e7be905045160b1b9fdca93dd6727b180feeafda6818e6496434ed8c5", size = 1613329, upload-time = "2025-11-04T12:42:12.928Z" }, { url = "https://files.pythonhosted.org/packages/d8/0f/30aef242fcab550b0b3520b8e3561156857c94288f0332a79928c31a52cf/greenlet-3.2.4-cp311-cp311-win_amd64.whl", hash = "sha256:9c40adce87eaa9ddb593ccb0fa6a07caf34015a29bf8d344811665b573138db9", size = 299100, upload-time = "2025-08-07T13:44:12.287Z" }, { url = "https://files.pythonhosted.org/packages/44/69/9b804adb5fd0671f367781560eb5eb586c4d495277c93bde4307b9e28068/greenlet-3.2.4-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:3b67ca49f54cede0186854a008109d6ee71f66bd57bb36abd6d0a0267b540cdd", size = 274079, upload-time = "2025-08-07T13:15:45.033Z" }, { url = "https://files.pythonhosted.org/packages/46/e9/d2a80c99f19a153eff70bc451ab78615583b8dac0754cfb942223d2c1a0d/greenlet-3.2.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ddf9164e7a5b08e9d22511526865780a576f19ddd00d62f8a665949327fde8bb", size = 640997, upload-time = "2025-08-07T13:42:56.234Z" }, @@ -869,8 +865,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/19/0d/6660d55f7373b2ff8152401a83e02084956da23ae58cddbfb0b330978fe9/greenlet-3.2.4-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b3812d8d0c9579967815af437d96623f45c0f2ae5f04e366de62a12d83a8fb0", size = 607586, upload-time = "2025-08-07T13:18:28.544Z" }, { url = "https://files.pythonhosted.org/packages/8e/1a/c953fdedd22d81ee4629afbb38d2f9d71e37d23caace44775a3a969147d4/greenlet-3.2.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:abbf57b5a870d30c4675928c37278493044d7c14378350b3aa5d484fa65575f0", size = 1123281, upload-time = "2025-08-07T13:42:39.858Z" }, { url = "https://files.pythonhosted.org/packages/3f/c7/12381b18e21aef2c6bd3a636da1088b888b97b7a0362fac2e4de92405f97/greenlet-3.2.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:20fb936b4652b6e307b8f347665e2c615540d4b42b3b4c8a321d8286da7e520f", size = 1151142, upload-time = "2025-08-07T13:18:22.981Z" }, - { url = "https://files.pythonhosted.org/packages/27/45/80935968b53cfd3f33cf99ea5f08227f2646e044568c9b1555b58ffd61c2/greenlet-3.2.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ee7a6ec486883397d70eec05059353b8e83eca9168b9f3f9a361971e77e0bcd0", size = 1564846, upload-time = "2025-11-04T12:42:15.191Z" }, - { url = "https://files.pythonhosted.org/packages/69/02/b7c30e5e04752cb4db6202a3858b149c0710e5453b71a3b2aec5d78a1aab/greenlet-3.2.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:326d234cbf337c9c3def0676412eb7040a35a768efc92504b947b3e9cfc7543d", size = 1633814, upload-time = "2025-11-04T12:42:17.175Z" }, { url = "https://files.pythonhosted.org/packages/e9/08/b0814846b79399e585f974bbeebf5580fbe59e258ea7be64d9dfb253c84f/greenlet-3.2.4-cp312-cp312-win_amd64.whl", hash = "sha256:a7d4e128405eea3814a12cc2605e0e6aedb4035bf32697f72deca74de4105e02", size = 299899, upload-time = "2025-08-07T13:38:53.448Z" }, ] @@ -1328,7 +1322,7 @@ wheels = [ [[package]] name = "nostrrelay" -version = "1.1.0" +version = "0.0.0" source = { virtual = "." } dependencies = [ { name = "lnbits" },