compact_v2_test.go raw
1 package crypto
2
3 import (
4 "testing"
5
6 "git.mleku.dev/mleku/dendrite/pkg/spore"
7 )
8
9 func TestSubsetRoundTrip(t *testing.T) {
10 tests := []struct {
11 name string
12 indices []int
13 n int
14 }{
15 {"single", []int{0}, 256},
16 {"first_last", []int{0, 255}, 256},
17 {"consecutive", []int{10, 11, 12, 13, 14}, 256},
18 {"spread", []int{0, 50, 100, 150, 200, 250}, 256},
19 {"large_k", func() []int {
20 idx := make([]int, 56)
21 for i := range idx {
22 idx[i] = i * 4 // every 4th site
23 }
24 return idx
25 }(), 256},
26 {"n512", []int{0, 100, 200, 300, 400, 500}, 512},
27 }
28
29 for _, tt := range tests {
30 t.Run(tt.name, func(t *testing.T) {
31 encoded := encodeSubset(tt.indices, tt.n)
32 decoded := decodeSubset(encoded, tt.n, len(tt.indices))
33
34 if len(decoded) != len(tt.indices) {
35 t.Fatalf("length mismatch: got %d, want %d", len(decoded), len(tt.indices))
36 }
37 for i := range tt.indices {
38 if decoded[i] != tt.indices[i] {
39 t.Errorf("index %d: got %d, want %d", i, decoded[i], tt.indices[i])
40 }
41 }
42 })
43 }
44 }
45
46 func TestSubsetEncodingSize(t *testing.T) {
47 // C(256,56) has 190 bits → 24 bytes.
48 nbytes := subsetBytes(256, 56)
49 t.Logf("C(256,56) encoding: %d bytes (%d bits)", nbytes, bigBinom(256, 56).BitLen())
50 if nbytes != 24 {
51 t.Errorf("subset encoding unexpected: %d bytes (want 24)", nbytes)
52 }
53
54 // C(512,56) has 251 bits → 32 bytes.
55 nbytes512 := subsetBytes(512, 56)
56 t.Logf("C(512,56) encoding: %d bytes (%d bits)", nbytes512, bigBinom(512, 56).BitLen())
57 if nbytes512 != 32 {
58 t.Errorf("subset encoding unexpected: %d bytes (want 32)", nbytes512)
59 }
60 }
61
62 func TestBitWriterReader(t *testing.T) {
63 bw := newBitWriter(16)
64 bw.writeBits(5, 3) // 101
65 bw.writeBits(200, 8) // 11001000
66 bw.writeBits(3, 3) // 011
67 bw.writeBits(0, 2) // 00
68
69 data := bw.bytes()
70
71 br := newBitReader(data)
72 v1, _ := br.readBits(3)
73 v2, _ := br.readBits(8)
74 v3, _ := br.readBits(3)
75 v4, _ := br.readBits(2)
76
77 if v1 != 5 {
78 t.Errorf("v1: got %d, want 5", v1)
79 }
80 if v2 != 200 {
81 t.Errorf("v2: got %d, want 200", v2)
82 }
83 if v3 != 3 {
84 t.Errorf("v3: got %d, want 3", v3)
85 }
86 if v4 != 0 {
87 t.Errorf("v4: got %d, want 0", v4)
88 }
89 }
90
91 func TestMarshalV2RoundTrip(t *testing.T) {
92 l := buildMatureLattice()
93 params := DefaultParams(Security128)
94 kp := GenerateKeyPair(l, params, testFactory)
95
96 msg := []byte("compact v2 round-trip")
97 sig, err := Sign(&kp.Private, msg, params)
98 if err != nil {
99 t.Fatalf("Sign: %v", err)
100 }
101
102 data, err := sig.MarshalV2(params.N)
103 if err != nil {
104 t.Fatalf("MarshalV2: %v", err)
105 }
106
107 sig2, consumed, err := UnmarshalSignatureV2(data)
108 if err != nil {
109 t.Fatalf("UnmarshalSignatureV2: %v", err)
110 }
111 if consumed != len(data) {
112 t.Errorf("consumed %d bytes, data is %d bytes", consumed, len(data))
113 }
114
115 // Occupied count must match.
116 origOcc := 0
117 for _, s := range sig.Response {
118 if s.Occupied {
119 origOcc++
120 }
121 }
122 if len(sig2.Response) != origOcc {
123 t.Errorf("occupied count: orig=%d, round-trip=%d", origOcc, len(sig2.Response))
124 }
125
126 // Per-site data must match for occupied sites.
127 origSites := make([]SiteMark, 0, origOcc)
128 for _, s := range sig.Response {
129 if s.Occupied {
130 origSites = append(origSites, s)
131 }
132 }
133
134 for i, s2 := range sig2.Response {
135 if i >= len(origSites) {
136 break
137 }
138 s1 := origSites[i]
139 if s2.Index != s1.Index {
140 t.Errorf("site %d: index %d != %d", i, s2.Index, s1.Index)
141 }
142 if s2.TypeTag != s1.TypeTag {
143 t.Errorf("site %d: tag %q != %q", i, s2.TypeTag, s1.TypeTag)
144 }
145 if s2.Projection != s1.Projection {
146 t.Errorf("site %d: projection %d != %d", i, s2.Projection, s1.Projection)
147 }
148 if s2.Perm != s1.Perm {
149 t.Errorf("site %d: perm %d != %d", i, s2.Perm, s1.Perm)
150 }
151 }
152
153 // Proof lengths must be consistent.
154 if len(sig2.Proof.LockIns) != len(sig2.Response) {
155 t.Error("proof/response length mismatch")
156 }
157 }
158
159 func TestMarshalV2Size(t *testing.T) {
160 l := buildMatureLattice()
161 params := DefaultParams(Security128)
162 kp := GenerateKeyPair(l, params, testFactory)
163
164 msg := []byte("size test v2")
165 sig, err := Sign(&kp.Private, msg, params)
166 if err != nil {
167 t.Fatalf("Sign: %v", err)
168 }
169
170 data, err := sig.MarshalV2(params.N)
171 if err != nil {
172 t.Fatalf("MarshalV2: %v", err)
173 }
174
175 v1data, err := sig.Marshal()
176 if err != nil {
177 t.Fatalf("Marshal V1: %v", err)
178 }
179
180 t.Logf("V1 compact: %d bytes", len(v1data))
181 t.Logf("V2 compact: %d bytes", len(data))
182 t.Logf("compression ratio: %.1f%%", 100.0*(1.0-float64(len(data))/float64(len(v1data))))
183
184 // V2 should be under 200 bytes for N=256.
185 if len(data) > 200 {
186 t.Errorf("V2 signature too large: %d bytes (want <= 200)", len(data))
187 }
188 }
189
190 func TestMarshalV2Deterministic(t *testing.T) {
191 l := buildMatureLattice()
192 params := DefaultParams(Security128)
193 kp := GenerateKeyPair(l, params, testFactory)
194
195 msg := []byte("deterministic v2")
196 sig, err := Sign(&kp.Private, msg, params)
197 if err != nil {
198 t.Fatalf("Sign: %v", err)
199 }
200
201 data1, err := sig.MarshalV2(params.N)
202 if err != nil {
203 t.Fatalf("MarshalV2 1: %v", err)
204 }
205 data2, err := sig.MarshalV2(params.N)
206 if err != nil {
207 t.Fatalf("MarshalV2 2: %v", err)
208 }
209
210 if string(data1) != string(data2) {
211 t.Error("MarshalV2 is not deterministic")
212 }
213 }
214
215 func TestVerifyCompactV2(t *testing.T) {
216 l := buildMatureLattice()
217 params := DefaultParams(Security128)
218 kp := GenerateKeyPair(l, params, testFactory)
219
220 msg := []byte("verify compact v2")
221 sig, err := Sign(&kp.Private, msg, params)
222 if err != nil {
223 t.Fatalf("Sign: %v", err)
224 }
225
226 data, err := sig.MarshalV2(params.N)
227 if err != nil {
228 t.Fatalf("MarshalV2: %v", err)
229 }
230
231 sig2, _, err := UnmarshalSignatureV2(data)
232 if err != nil {
233 t.Fatalf("UnmarshalSignatureV2: %v", err)
234 }
235
236 fp := FingerprintFromSpore(spore.Extract(kp.Private.Lattice))
237 if !VerifyCompactV2(fp, msg, sig2) {
238 t.Error("VerifyCompactV2 failed on valid signature")
239 }
240
241 // Wrong message should fail.
242 if VerifyCompactV2(fp, []byte("wrong message"), sig2) {
243 t.Error("VerifyCompactV2 accepted wrong message")
244 }
245 }
246
247 func TestMarshalV2NilSignature(t *testing.T) {
248 var s *Signature
249 _, err := s.MarshalV2(256)
250 if err == nil {
251 t.Error("expected error marshaling nil signature")
252 }
253 }
254
255 func TestUnmarshalV2Truncated(t *testing.T) {
256 _, _, err := UnmarshalSignatureV2([]byte{0xD2, 0x00})
257 if err == nil {
258 t.Error("expected error with truncated data")
259 }
260 }
261
262 func TestUnmarshalV2BadMagic(t *testing.T) {
263 _, _, err := UnmarshalSignatureV2([]byte{0xFF, 0x00, 0x01, 0x01, 0x01, 'x', 0x01, 0x00})
264 if err == nil {
265 t.Error("expected error with bad magic")
266 }
267 }
268