A Python port of the Invisible Internet Project (I2P)
1"""Tests for EdDSA blinding primitives."""
2
3import pytest
4
5from i2p_crypto.eddsa_blinding import EdDSABlinding, HAS_NACL
6
7pytestmark = pytest.mark.skipif(not HAS_NACL, reason="pynacl not installed")
8
9
10def test_reduce_returns_32_bytes():
11 data_64 = b"\x01" * 64
12 result = EdDSABlinding.reduce(data_64)
13 assert isinstance(result, bytes)
14 assert len(result) == 32
15
16
17def test_scalar_mult_base_returns_32_bytes():
18 # Use a known clamped scalar
19 scalar = EdDSABlinding.seed_to_scalar(b"\xaa" * 32)
20 pub = EdDSABlinding.scalar_mult_base(scalar)
21 assert isinstance(pub, bytes)
22 assert len(pub) == 32
23
24
25def test_scalar_mult_base_deterministic():
26 scalar = EdDSABlinding.seed_to_scalar(b"\xbb" * 32)
27 pub1 = EdDSABlinding.scalar_mult_base(scalar)
28 pub2 = EdDSABlinding.scalar_mult_base(scalar)
29 assert pub1 == pub2
30
31
32def test_blind_private_roundtrip_with_public():
33 """blind(priv, alpha) * G == blind(pub, alpha * G)."""
34 seed_a = b"\x11" * 32
35 seed_alpha = b"\x22" * 32
36
37 scalar_a = EdDSABlinding.seed_to_scalar(seed_a)
38 scalar_alpha = EdDSABlinding.seed_to_scalar(seed_alpha)
39
40 # Method 1: blind private, then derive public
41 blinded_priv = EdDSABlinding.blind_private(scalar_a, scalar_alpha)
42 pub_from_priv = EdDSABlinding.scalar_mult_base(blinded_priv)
43
44 # Method 2: derive publics, then blind (add points)
45 pub_a = EdDSABlinding.scalar_mult_base(scalar_a)
46 pub_alpha = EdDSABlinding.scalar_mult_base(scalar_alpha)
47 pub_from_pub = EdDSABlinding.blind_public(pub_a, pub_alpha)
48
49 assert pub_from_priv == pub_from_pub
50
51
52def test_seed_to_scalar_clamping():
53 """Verify the scalar is properly clamped per RFC 8032."""
54 scalar = EdDSABlinding.seed_to_scalar(b"\xff" * 32)
55 assert scalar[0] & 0x07 == 0 # bottom 3 bits cleared
56 assert scalar[31] & 0x80 == 0 # top bit cleared
57 assert scalar[31] & 0x40 == 0x40 # second-to-top bit set