import hashlib import json from typing import List, Optional from pydantic import BaseModel from secp256k1 import PublicKey class NostrEvent(BaseModel): id: str = "" pubkey: str created_at: int kind: int tags: List[List[str]] = [] content: str = "" sig: Optional[str] = None def serialize(self) -> List: return [0, self.pubkey, self.created_at, self.kind, self.tags, self.content] def serialize_json(self) -> str: e = self.serialize() return json.dumps(e, separators=(",", ":"), ensure_ascii=False) @property def event_id(self) -> str: data = self.serialize_json() id = hashlib.sha256(data.encode()).hexdigest() return id def check_signature(self): event_id = self.event_id if self.id != event_id: raise ValueError( f"Invalid event id. Expected: '{event_id}' got '{self.id}'" ) try: pub_key = PublicKey(bytes.fromhex("02" + self.pubkey), True) except Exception: raise ValueError( f"Invalid public key: '{self.pubkey}' for event '{self.id}'" ) valid_signature = self.sig and 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}'") def stringify(self) -> str: return json.dumps(dict(self)) def tag_values(self, tag_name: str) -> List[str]: return [t[1] for t in self.tags if t[0] == tag_name] def has_tag_value(self, tag_name: str, tag_value: str) -> bool: return tag_value in self.tag_values(tag_name)