compact_test.go raw
1 package crypto
2
3 import (
4 "testing"
5
6 "git.mleku.dev/mleku/dendrite/pkg/ratio"
7 )
8
9 func TestMarshalUnmarshal(t *testing.T) {
10 l := buildMatureLattice()
11 params := DefaultParams(Security128)
12 kp := GenerateKeyPair(l, params, testFactory)
13
14 msg := []byte("compact round-trip")
15 sig, err := Sign(&kp.Private, msg, params)
16 if err != nil {
17 t.Fatalf("Sign: %v", err)
18 }
19
20 data, err := sig.Marshal()
21 if err != nil {
22 t.Fatalf("Marshal: %v", err)
23 }
24
25 sig2, err := UnmarshalSignature(data)
26 if err != nil {
27 t.Fatalf("Unmarshal: %v", err)
28 }
29
30 // Challenge must round-trip exactly.
31 if sig2.Challenge != sig.Challenge {
32 t.Error("challenge mismatch after round-trip")
33 }
34
35 // Occupied count must match.
36 origOcc := 0
37 for _, s := range sig.Response {
38 if s.Occupied {
39 origOcc++
40 }
41 }
42 rtOcc := 0
43 for _, s := range sig2.Response {
44 if s.Occupied {
45 rtOcc++
46 }
47 }
48 if origOcc != rtOcc {
49 t.Errorf("occupied count: orig=%d, round-trip=%d", origOcc, rtOcc)
50 }
51
52 // Proof lengths must be consistent.
53 if len(sig2.Proof.LockIns) != len(sig2.Proof.NeighborCounts) {
54 t.Error("proof length mismatch in round-trip")
55 }
56 if len(sig2.Proof.LockIns) != len(sig2.Proof.HexTrace) {
57 t.Error("proof/hex trace length mismatch in round-trip")
58 }
59 }
60
61 func TestMarshalSize(t *testing.T) {
62 l := buildMatureLattice()
63 params := DefaultParams(Security128)
64 kp := GenerateKeyPair(l, params, testFactory)
65
66 msg := []byte("size test")
67 sig, err := Sign(&kp.Private, msg, params)
68 if err != nil {
69 t.Fatalf("Sign: %v", err)
70 }
71
72 data, err := sig.Marshal()
73 if err != nil {
74 t.Fatalf("Marshal: %v", err)
75 }
76
77 t.Logf("compact signature size: %d bytes", len(data))
78
79 // For Security128 (N=256), compact should be well under 2 KB.
80 if len(data) > 2048 {
81 t.Errorf("compact signature too large: %d bytes (want <= 2048)", len(data))
82 }
83 }
84
85 func TestMarshalNilSignature(t *testing.T) {
86 var s *Signature
87 _, err := s.Marshal()
88 if err == nil {
89 t.Error("expected error marshaling nil signature")
90 }
91 }
92
93 func TestUnmarshalTruncated(t *testing.T) {
94 _, err := UnmarshalSignature([]byte{1, 2, 3})
95 if err == nil {
96 t.Error("expected error with truncated data")
97 }
98 }
99
100 func TestQuantizeLockIn(t *testing.T) {
101 tests := []struct {
102 name string
103 num int64
104 den int64
105 want uint8
106 }{
107 {"zero", 0, 1, 0},
108 {"half", 1, 2, 127},
109 {"one", 1, 1, 255},
110 {"quarter", 1, 4, 63},
111 {"over_one", 3, 2, 255}, // clamped
112 }
113 for _, tt := range tests {
114 t.Run(tt.name, func(t *testing.T) {
115 r := ratio.New(tt.num, tt.den)
116 got := quantizeLockIn(r)
117 if got != tt.want {
118 t.Errorf("quantizeLockIn(%s) = %d, want %d", r, got, tt.want)
119 }
120 })
121 }
122 }
123
124 func TestDequantizeRoundTrip(t *testing.T) {
125 // Quantize then dequantize should be approximately equal.
126 r := ratio.New(3, 7)
127 q := quantizeLockIn(r)
128 back := dequantizeLockIn(q)
129
130 // The dequantized value should be within 1/255 of the original.
131 diff := r.Sub(back).Abs()
132 tolerance := ratio.New(2, 255)
133 if diff.Greater(tolerance) {
134 t.Errorf("round-trip error too large: %s → %d → %s (diff=%s)",
135 r, q, back, diff)
136 }
137 }
138
139 func TestMarshalDeterministic(t *testing.T) {
140 l := buildMatureLattice()
141 params := DefaultParams(Security128)
142 kp := GenerateKeyPair(l, params, testFactory)
143
144 msg := []byte("deterministic test")
145 sig, err := Sign(&kp.Private, msg, params)
146 if err != nil {
147 t.Fatalf("Sign: %v", err)
148 }
149
150 data1, err := sig.Marshal()
151 if err != nil {
152 t.Fatalf("Marshal 1: %v", err)
153 }
154 data2, err := sig.Marshal()
155 if err != nil {
156 t.Fatalf("Marshal 2: %v", err)
157 }
158
159 if string(data1) != string(data2) {
160 t.Error("marshaling the same signature twice should produce identical bytes")
161 }
162 }
163