Files
Timmy-time-dashboard/tests/unit/test_nostr_event.py
Timmy Time 4d2aeb937f
Some checks failed
Tests / lint (push) Successful in 11s
Tests / test (push) Has been cancelled
[loop-cycle-7] refactor: split research.py into research/ subpackage (#1405) (#1458)
2026-03-24 19:53:04 +00:00

178 lines
6.1 KiB
Python

"""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"