forked from Rockachopa/Timmy-time-dashboard
Co-authored-by: Claude (Opus 4.6) <claude@hermes.local> Co-committed-by: Claude (Opus 4.6) <claude@hermes.local>
127 lines
3.9 KiB
Python
127 lines
3.9 KiB
Python
"""Unit tests for infrastructure.nostr.keypair."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
from infrastructure.nostr.keypair import (
|
|
NostrKeypair,
|
|
_bech32_decode,
|
|
_bech32_encode,
|
|
generate_keypair,
|
|
load_keypair,
|
|
pubkey_from_privkey,
|
|
)
|
|
|
|
|
|
class TestGenerateKeypair:
|
|
def test_returns_nostr_keypair(self):
|
|
kp = generate_keypair()
|
|
assert isinstance(kp, NostrKeypair)
|
|
|
|
def test_privkey_hex_is_64_chars(self):
|
|
kp = generate_keypair()
|
|
assert len(kp.privkey_hex) == 64
|
|
assert all(c in "0123456789abcdef" for c in kp.privkey_hex)
|
|
|
|
def test_pubkey_hex_is_64_chars(self):
|
|
kp = generate_keypair()
|
|
assert len(kp.pubkey_hex) == 64
|
|
assert all(c in "0123456789abcdef" for c in kp.pubkey_hex)
|
|
|
|
def test_nsec_starts_with_nsec1(self):
|
|
kp = generate_keypair()
|
|
assert kp.nsec.startswith("nsec1")
|
|
|
|
def test_npub_starts_with_npub1(self):
|
|
kp = generate_keypair()
|
|
assert kp.npub.startswith("npub1")
|
|
|
|
def test_two_keypairs_are_different(self):
|
|
kp1 = generate_keypair()
|
|
kp2 = generate_keypair()
|
|
assert kp1.privkey_hex != kp2.privkey_hex
|
|
assert kp1.pubkey_hex != kp2.pubkey_hex
|
|
|
|
def test_privkey_bytes_matches_hex(self):
|
|
kp = generate_keypair()
|
|
assert kp.privkey_bytes == bytes.fromhex(kp.privkey_hex)
|
|
|
|
def test_pubkey_bytes_matches_hex(self):
|
|
kp = generate_keypair()
|
|
assert kp.pubkey_bytes == bytes.fromhex(kp.pubkey_hex)
|
|
|
|
|
|
class TestLoadKeypair:
|
|
def test_round_trip_via_privkey_hex(self):
|
|
kp1 = generate_keypair()
|
|
kp2 = load_keypair(privkey_hex=kp1.privkey_hex)
|
|
assert kp2.privkey_hex == kp1.privkey_hex
|
|
assert kp2.pubkey_hex == kp1.pubkey_hex
|
|
|
|
def test_round_trip_via_nsec(self):
|
|
kp1 = generate_keypair()
|
|
kp2 = load_keypair(nsec=kp1.nsec)
|
|
assert kp2.privkey_hex == kp1.privkey_hex
|
|
assert kp2.pubkey_hex == kp1.pubkey_hex
|
|
|
|
def test_raises_if_both_supplied(self):
|
|
kp = generate_keypair()
|
|
with pytest.raises(ValueError, match="either"):
|
|
load_keypair(privkey_hex=kp.privkey_hex, nsec=kp.nsec)
|
|
|
|
def test_raises_if_neither_supplied(self):
|
|
with pytest.raises(ValueError, match="either"):
|
|
load_keypair()
|
|
|
|
def test_raises_on_invalid_hex(self):
|
|
with pytest.raises((ValueError, Exception)):
|
|
load_keypair(privkey_hex="zzzz")
|
|
|
|
def test_raises_on_wrong_length_hex(self):
|
|
with pytest.raises(ValueError):
|
|
load_keypair(privkey_hex="deadbeef") # too short
|
|
|
|
def test_raises_on_wrong_hrp_bech32(self):
|
|
kp = generate_keypair()
|
|
# npub is bech32 but with hrp "npub", not "nsec"
|
|
with pytest.raises(ValueError):
|
|
load_keypair(nsec=kp.npub)
|
|
|
|
def test_npub_derived_from_privkey(self):
|
|
kp1 = generate_keypair()
|
|
kp2 = load_keypair(privkey_hex=kp1.privkey_hex)
|
|
assert kp2.npub == kp1.npub
|
|
|
|
|
|
class TestPubkeyFromPrivkey:
|
|
def test_derives_correct_pubkey(self):
|
|
kp = generate_keypair()
|
|
derived = pubkey_from_privkey(kp.privkey_hex)
|
|
assert derived == kp.pubkey_hex
|
|
|
|
def test_is_deterministic(self):
|
|
kp = generate_keypair()
|
|
assert pubkey_from_privkey(kp.privkey_hex) == pubkey_from_privkey(kp.privkey_hex)
|
|
|
|
|
|
class TestBech32:
|
|
def test_encode_decode_round_trip(self):
|
|
data = bytes(range(32))
|
|
encoded = _bech32_encode("test", data)
|
|
hrp, decoded = _bech32_decode(encoded)
|
|
assert hrp == "test"
|
|
assert decoded == data
|
|
|
|
def test_invalid_checksum_raises(self):
|
|
kp = generate_keypair()
|
|
mangled = kp.npub[:-1] + ("q" if kp.npub[-1] != "q" else "p")
|
|
with pytest.raises(ValueError, match="checksum"):
|
|
_bech32_decode(mangled)
|
|
|
|
def test_npub_roundtrip(self):
|
|
kp = generate_keypair()
|
|
hrp, pub = _bech32_decode(kp.npub)
|
|
assert hrp == "npub"
|
|
assert pub.hex() == kp.pubkey_hex
|