From d9fb70e8edf52caa63e2665cbda637dc73f5add4 Mon Sep 17 00:00:00 2001 From: kdmukai Date: Thu, 19 Jan 2023 17:28:57 -0600 Subject: [PATCH] Integrate Event and RelayManager w/validity checking (#30) --- README.md | 3 +-- nostr/event.py | 27 +++++++++++++++++---------- nostr/relay_manager.py | 22 +++++++++++++++++++++- test/test_relay_manager.py | 30 ++++++++++++++++++++++++++++++ 4 files changed, 69 insertions(+), 13 deletions(-) create mode 100644 test/test_relay_manager.py diff --git a/README.md b/README.md index f00def5..42f7486 100644 --- a/README.md +++ b/README.md @@ -51,8 +51,7 @@ private_key = PrivateKey() event = Event(private_key.public_key.hex(), "Hello Nostr") event.sign(private_key.hex()) -message = json.dumps([ClientMessageType.EVENT, event.to_json_object()]) -relay_manager.publish_message(message) +relay_manager.publish_event(event) time.sleep(1) # allow the messages to send relay_manager.close_connections() diff --git a/nostr/event.py b/nostr/event.py index b5ee4f6..10d3829 100644 --- a/nostr/event.py +++ b/nostr/event.py @@ -4,6 +4,8 @@ from enum import IntEnum from secp256k1 import PrivateKey, PublicKey from hashlib import sha256 +from nostr.message_type import ClientMessageType + class EventKind(IntEnum): SET_METADATA = 0 @@ -55,13 +57,18 @@ class Event(): event_id = Event.compute_id(self.public_key, self.created_at, self.kind, self.tags, self.content) return pub_key.schnorr_verify(bytes.fromhex(event_id), bytes.fromhex(self.signature), None, raw=True) - def to_json_object(self) -> dict: - return { - "id": self.id, - "pubkey": self.public_key, - "created_at": self.created_at, - "kind": self.kind, - "tags": self.tags, - "content": self.content, - "sig": self.signature - } + def to_message(self) -> str: + return json.dumps( + [ + ClientMessageType.EVENT, + { + "id": self.id, + "pubkey": self.public_key, + "created_at": self.created_at, + "kind": self.kind, + "tags": self.tags, + "content": self.content, + "sig": self.signature + } + ] + ) diff --git a/nostr/relay_manager.py b/nostr/relay_manager.py index e4d177e..c0664f6 100644 --- a/nostr/relay_manager.py +++ b/nostr/relay_manager.py @@ -1,8 +1,19 @@ +import json import threading + +from .event import Event from .filter import Filters from .message_pool import MessagePool +from .message_type import ClientMessageType from .relay import Relay, RelayPolicy + + +class RelayException(Exception): + pass + + + class RelayManager: def __init__(self) -> None: self.relays: dict[str, Relay] = {} @@ -40,4 +51,13 @@ class RelayManager: for relay in self.relays.values(): if relay.policy.should_write: relay.publish(message) - + + def publish_event(self, event: Event): + """ Verifies that the Event is publishable before submitting it to relays """ + if event.signature is None: + raise RelayException(f"Could not publish {event.id}: must be signed") + + if not event.verify(): + raise RelayException(f"Could not publish {event.id}: failed to verify signature {event.signature}") + + self.publish_message(event.to_message()) diff --git a/test/test_relay_manager.py b/test/test_relay_manager.py new file mode 100644 index 0000000..c6e838a --- /dev/null +++ b/test/test_relay_manager.py @@ -0,0 +1,30 @@ +import pytest +from nostr.event import Event +from nostr.key import PrivateKey +from nostr.relay_manager import RelayManager, RelayException + + +def test_only_relay_valid_events(): + """ publish_event raise a RelayException if an Event fails verification """ + pk = PrivateKey() + event = Event( + public_key=pk.public_key.hex(), + content="Hello, world!", + ) + + relay_manager = RelayManager() + + # Deliberately forget to sign the Event + with pytest.raises(RelayException) as e: + relay_manager.publish_event(event) + assert "must be signed" in str(e) + + # Attempt to relay with a nonsense signature + event.signature = '0' * 32 + with pytest.raises(RelayException) as e: + relay_manager.publish_event(event) + assert "failed to verify" in str(e) + + # Properly signed Event can be relayed + event.sign(pk.hex()) + relay_manager.publish_event(event)