"""Unit tests for infrastructure.nostr.event.""" from __future__ import annotations import hashlib import json import time import pytest from infrastructure.nostr.event import ( _event_hash, build_event, schnorr_sign, schnorr_verify, ) from infrastructure.nostr.keypair import generate_keypair class TestSchorrSign: def test_returns_64_bytes(self): kp = generate_keypair() msg = b"\x00" * 32 sig = schnorr_sign(msg, kp.privkey_bytes) assert len(sig) == 64 def test_different_msg_different_sig(self): kp = generate_keypair() sig1 = schnorr_sign(b"\x01" * 32, kp.privkey_bytes) sig2 = schnorr_sign(b"\x02" * 32, kp.privkey_bytes) assert sig1 != sig2 def test_raises_on_wrong_msg_length(self): kp = generate_keypair() with pytest.raises(ValueError, match="32 bytes"): schnorr_sign(b"too short", kp.privkey_bytes) def test_raises_on_wrong_key_length(self): msg = b"\x00" * 32 with pytest.raises(ValueError, match="32 bytes"): schnorr_sign(msg, b"too short") def test_nondeterministic_due_to_randomness(self): # BIP-340 uses auxiliary randomness; repeated calls produce different sigs kp = generate_keypair() msg = b"\x42" * 32 sig1 = schnorr_sign(msg, kp.privkey_bytes) sig2 = schnorr_sign(msg, kp.privkey_bytes) # With different random nonces these should differ (astronomically unlikely to collide) # We just verify both are valid assert schnorr_verify(msg, kp.pubkey_bytes, sig1) assert schnorr_verify(msg, kp.pubkey_bytes, sig2) class TestSchnorrVerify: def test_valid_signature_verifies(self): kp = generate_keypair() msg = hashlib.sha256(b"hello nostr").digest() sig = schnorr_sign(msg, kp.privkey_bytes) assert schnorr_verify(msg, kp.pubkey_bytes, sig) is True def test_wrong_pubkey_fails(self): kp1 = generate_keypair() kp2 = generate_keypair() msg = b"\x00" * 32 sig = schnorr_sign(msg, kp1.privkey_bytes) assert schnorr_verify(msg, kp2.pubkey_bytes, sig) is False def test_tampered_sig_fails(self): kp = generate_keypair() msg = b"\x00" * 32 sig = bytearray(schnorr_sign(msg, kp.privkey_bytes)) sig[0] ^= 0xFF assert schnorr_verify(msg, kp.pubkey_bytes, bytes(sig)) is False def test_tampered_msg_fails(self): kp = generate_keypair() msg = b"\x00" * 32 sig = schnorr_sign(msg, kp.privkey_bytes) bad_msg = b"\xff" * 32 assert schnorr_verify(bad_msg, kp.pubkey_bytes, sig) is False def test_wrong_lengths_return_false(self): kp = generate_keypair() msg = b"\x00" * 32 sig = schnorr_sign(msg, kp.privkey_bytes) assert schnorr_verify(msg[:16], kp.pubkey_bytes, sig) is False assert schnorr_verify(msg, kp.pubkey_bytes[:16], sig) is False assert schnorr_verify(msg, kp.pubkey_bytes, sig[:32]) is False def test_never_raises(self): # Should return False for any garbage input, not raise assert schnorr_verify(b"x", b"y", b"z") is False class TestEventHash: def test_returns_32_bytes(self): h = _event_hash("aabbcc", 0, 1, [], "") assert len(h) == 32 def test_deterministic(self): h1 = _event_hash("aa", 1, 1, [], "hello") h2 = _event_hash("aa", 1, 1, [], "hello") assert h1 == h2 def test_different_content_different_hash(self): h1 = _event_hash("aa", 1, 1, [], "hello") h2 = _event_hash("aa", 1, 1, [], "world") assert h1 != h2 class TestBuildEvent: def test_returns_required_fields(self): kp = generate_keypair() ev = build_event(kind=1, content="hello", keypair=kp) assert set(ev) >= {"id", "pubkey", "created_at", "kind", "tags", "content", "sig"} def test_kind_matches(self): kp = generate_keypair() ev = build_event(kind=0, content="{}", keypair=kp) assert ev["kind"] == 0 def test_pubkey_matches_keypair(self): kp = generate_keypair() ev = build_event(kind=1, content="x", keypair=kp) assert ev["pubkey"] == kp.pubkey_hex def test_id_is_64_char_hex(self): kp = generate_keypair() ev = build_event(kind=1, content="x", keypair=kp) assert len(ev["id"]) == 64 assert all(c in "0123456789abcdef" for c in ev["id"]) def test_sig_is_128_char_hex(self): kp = generate_keypair() ev = build_event(kind=1, content="x", keypair=kp) assert len(ev["sig"]) == 128 assert all(c in "0123456789abcdef" for c in ev["sig"]) def test_signature_verifies(self): kp = generate_keypair() ev = build_event(kind=1, content="test", keypair=kp) sig_bytes = bytes.fromhex(ev["sig"]) id_bytes = bytes.fromhex(ev["id"]) assert schnorr_verify(id_bytes, kp.pubkey_bytes, sig_bytes) def test_id_matches_canonical_hash(self): kp = generate_keypair() ts = int(time.time()) ev = build_event(kind=1, content="hi", keypair=kp, created_at=ts) expected_hash = _event_hash(kp.pubkey_hex, ts, 1, [], "hi").hex() assert ev["id"] == expected_hash def test_custom_tags(self): kp = generate_keypair() tags = [["t", "gaming"], ["r", "wss://relay.example.com"]] ev = build_event(kind=1, content="x", keypair=kp, tags=tags) assert ev["tags"] == tags def test_default_tags_empty(self): kp = generate_keypair() ev = build_event(kind=1, content="x", keypair=kp) assert ev["tags"] == [] def test_custom_created_at(self): kp = generate_keypair() ts = 1700000000 ev = build_event(kind=1, content="x", keypair=kp, created_at=ts) assert ev["created_at"] == ts def test_kind0_profile_content_is_json(self): kp = generate_keypair() profile = {"name": "Timmy", "about": "test"} ev = build_event(kind=0, content=json.dumps(profile), keypair=kp) assert ev["kind"] == 0 parsed = json.loads(ev["content"]) assert parsed["name"] == "Timmy"