A Python port of the Invisible Internet Project (I2P)
1"""Tests for Blinding API and BlindData."""
2
3import pytest
4from i2p_crypto.eddsa_blinding import HAS_NACL
5
6pytestmark = pytest.mark.skipif(not HAS_NACL, reason="pynacl not installed")
7
8from i2p_crypto.dsa import SigType
9from i2p_crypto.blinding import Blinding
10from i2p_crypto.eddsa_blinding import EdDSABlinding
11from i2p_data.key_types import SigningPublicKey, SigningPrivateKey
12from i2p_data.blind_data import BlindData, AuthType
13
14
15def _make_ed25519_spk():
16 """Create a test Ed25519 signing public key."""
17 seed = b"\x42" * 32
18 scalar = EdDSABlinding.seed_to_scalar(seed)
19 pub = EdDSABlinding.scalar_mult_base(scalar)
20 return SigningPublicKey(pub, sig_type=SigType.EdDSA_SHA512_Ed25519), seed
21
22
23def test_generate_alpha_deterministic():
24 spk, _ = _make_ed25519_spk()
25 ts = 1700000000
26 a1 = Blinding.generate_alpha(spk, timestamp_sec=ts)
27 a2 = Blinding.generate_alpha(spk, timestamp_sec=ts)
28 assert a1.to_bytes() == a2.to_bytes()
29 assert len(a1.to_bytes()) == 32
30
31
32def test_generate_alpha_daily_rotation():
33 spk, _ = _make_ed25519_spk()
34 a1 = Blinding.generate_alpha(spk, timestamp_sec=1700000000)
35 a2 = Blinding.generate_alpha(spk, timestamp_sec=1700086400) # +1 day
36 assert a1.to_bytes() != a2.to_bytes()
37
38
39def test_secret_changes_alpha():
40 spk, _ = _make_ed25519_spk()
41 ts = 1700000000
42 a1 = Blinding.generate_alpha(spk, timestamp_sec=ts)
43 a2 = Blinding.generate_alpha(spk, timestamp_sec=ts, secret="mysecret")
44 assert a1.to_bytes() != a2.to_bytes()
45
46
47def test_blind_roundtrip():
48 """blind(priv, alpha) * G == blind(pub, alpha*G)."""
49 spk, seed = _make_ed25519_spk()
50 ts = 1700000000
51 alpha = Blinding.generate_alpha(spk, timestamp_sec=ts)
52
53 blinded_pub = Blinding.blind(spk, alpha)
54 blinded_priv = Blinding.unblind(seed, alpha)
55
56 # Verify consistency: blinded_priv * G == blinded_pub
57 pub_from_priv = EdDSABlinding.scalar_mult_base(blinded_priv.to_bytes())
58 assert pub_from_priv == blinded_pub.to_bytes()
59
60
61def test_b32_encode_decode_roundtrip():
62 spk, _ = _make_ed25519_spk()
63 address = Blinding.encode_b32(spk)
64 assert address.endswith(".b32.i2p")
65 assert len(address) > 10
66
67 sig_in, sig_out, decoded_spk, flags = Blinding.decode_b32(address)
68 assert sig_in == SigType.EdDSA_SHA512_Ed25519
69 assert sig_out == SigType.RedDSA_SHA512_Ed25519
70 assert decoded_spk.to_bytes() == spk.to_bytes()
71 assert flags == 0
72
73
74def test_b32_with_flags():
75 spk, _ = _make_ed25519_spk()
76 address = Blinding.encode_b32(spk, secret_required=True, per_client_auth=True)
77 _, _, _, flags = Blinding.decode_b32(address)
78 assert flags & 0x02 # secret_required
79 assert flags & 0x04 # per_client_auth
80
81
82# --- BlindData tests ---
83
84def test_blind_data_caching():
85 spk, _ = _make_ed25519_spk()
86 bd = BlindData(SigType.EdDSA_SHA512_Ed25519, SigType.RedDSA_SHA512_Ed25519, spk)
87 ts = 1700000000
88 bp1 = bd.get_blinded_pubkey(ts)
89 bp2 = bd.get_blinded_pubkey(ts)
90 assert bp1.to_bytes() == bp2.to_bytes()
91
92
93def test_blind_data_daily_rotation():
94 spk, _ = _make_ed25519_spk()
95 bd = BlindData(SigType.EdDSA_SHA512_Ed25519, SigType.RedDSA_SHA512_Ed25519, spk)
96 bp1 = bd.get_blinded_pubkey(1700000000)
97 bp2 = bd.get_blinded_pubkey(1700086400)
98 assert bp1.to_bytes() != bp2.to_bytes()
99
100
101def test_credential_deterministic():
102 spk, _ = _make_ed25519_spk()
103 bd = BlindData(SigType.EdDSA_SHA512_Ed25519, SigType.RedDSA_SHA512_Ed25519, spk)
104 c1 = bd.get_credential()
105 c2 = bd.get_credential()
106 assert c1 == c2
107 assert len(c1) == 32
108
109
110def test_subcredential_changes_daily():
111 spk, _ = _make_ed25519_spk()
112 bd = BlindData(SigType.EdDSA_SHA512_Ed25519, SigType.RedDSA_SHA512_Ed25519, spk)
113 sc1 = bd.get_subcredential(1700000000)
114 sc2 = bd.get_subcredential(1700086400)
115 assert sc1 != sc2
116
117
118def test_destination_hash():
119 spk, _ = _make_ed25519_spk()
120 bd = BlindData(SigType.EdDSA_SHA512_Ed25519, SigType.RedDSA_SHA512_Ed25519, spk)
121 dh = bd.get_destination_hash(1700000000)
122 assert len(dh) == 32