sign_test.go raw
1 package crypto
2
3 import (
4 "testing"
5
6 "git.mleku.dev/mleku/dendrite/pkg/ratio"
7 "git.mleku.dev/mleku/dendrite/pkg/state"
8 )
9
10 func TestSignProducesSignature(t *testing.T) {
11 l := buildMatureLattice()
12 params := DefaultParams(Security128)
13 kp := GenerateKeyPair(l, params, testFactory)
14
15 sig, err := Sign(&kp.Private, []byte("test message"), params)
16 if err != nil {
17 t.Fatalf("Sign: %v", err)
18 }
19 if sig == nil {
20 t.Fatal("signature should not be nil")
21 }
22 if sig.Fingerprint.Hash == "" {
23 t.Error("fingerprint hash should not be empty")
24 }
25
26 expected := Hash([]byte("test message"))
27 if sig.Challenge != expected {
28 t.Error("challenge should be Hamadryad hash of message")
29 }
30 }
31
32 func TestSignHasProof(t *testing.T) {
33 l := buildMatureLattice()
34 params := DefaultParams(Security128)
35 kp := GenerateKeyPair(l, params, testFactory)
36
37 sig, err := Sign(&kp.Private, []byte("proof test"), params)
38 if err != nil {
39 t.Fatalf("Sign: %v", err)
40 }
41
42 // The lattice already has bonded elements, so proof should have data.
43 if len(sig.Proof.LockIns) == 0 {
44 t.Error("proof should have lock-in depths")
45 }
46 if len(sig.Proof.NeighborCounts) == 0 {
47 t.Error("proof should have neighbor counts")
48 }
49 if len(sig.Proof.HexTrace) == 0 {
50 t.Error("proof should have hexagram trace")
51 }
52 }
53
54 func TestVerifyValidSignature(t *testing.T) {
55 l := buildMatureLattice()
56 params := DefaultParams(Security128)
57 kp := GenerateKeyPair(l, params, testFactory)
58
59 msg := []byte("verify me")
60 sig, err := Sign(&kp.Private, msg, params)
61 if err != nil {
62 t.Fatalf("Sign: %v", err)
63 }
64
65 fp := FingerprintFromSpore(kp.Public.Spore)
66 if !Verify(fp, msg, sig) {
67 t.Error("valid signature should verify")
68 }
69 }
70
71 func TestVerifyTamperedMessage(t *testing.T) {
72 l := buildMatureLattice()
73 params := DefaultParams(Security128)
74 kp := GenerateKeyPair(l, params, testFactory)
75
76 msg := []byte("original")
77 sig, err := Sign(&kp.Private, msg, params)
78 if err != nil {
79 t.Fatalf("Sign: %v", err)
80 }
81
82 fp := FingerprintFromSpore(kp.Public.Spore)
83 if Verify(fp, []byte("tampered"), sig) {
84 t.Error("tampered message should not verify")
85 }
86 }
87
88 func TestVerifyWrongFingerprint(t *testing.T) {
89 l := buildMatureLattice()
90 params := DefaultParams(Security128)
91 kp := GenerateKeyPair(l, params, testFactory)
92
93 msg := []byte("test")
94 sig, err := Sign(&kp.Private, msg, params)
95 if err != nil {
96 t.Fatalf("Sign: %v", err)
97 }
98
99 // Different fingerprint.
100 wrongFP := SporeFingerprint{Hash: "wrong_hash"}
101 if Verify(wrongFP, msg, sig) {
102 t.Error("wrong fingerprint should not verify")
103 }
104 }
105
106 func TestVerifyNilSignature(t *testing.T) {
107 fp := SporeFingerprint{Hash: "test"}
108 if Verify(fp, []byte("msg"), nil) {
109 t.Error("nil signature should not verify")
110 }
111 }
112
113 func TestFingerprintFromSpore(t *testing.T) {
114 l := buildMatureLattice()
115 params := DefaultParams(Security128)
116 kp := GenerateKeyPair(l, params, testFactory)
117
118 fp := FingerprintFromSpore(kp.Public.Spore)
119 if fp.Hash == "" {
120 t.Error("fingerprint hash should not be empty")
121 }
122 if len(fp.TypeSignature) == 0 {
123 t.Error("fingerprint should have type signature")
124 }
125 }
126
127 func TestSortedTags(t *testing.T) {
128 tags := sortedTags(kp().Public.Spore.TypeSignature)
129 for i := 1; i < len(tags); i++ {
130 if tags[i] < tags[i-1] {
131 t.Errorf("tags not sorted: %q after %q", tags[i], tags[i-1])
132 }
133 }
134 }
135
136 func kp() *KeyPair {
137 l := buildMatureLattice()
138 return GenerateKeyPair(l, DefaultParams(Security128), testFactory)
139 }
140
141 func TestSignNilLattice(t *testing.T) {
142 privkey := &PrivateKey{Lattice: nil, ConstraintFactory: testFactory}
143 _, err := Sign(privkey, []byte("test"), DefaultParams(Security128))
144 if err == nil {
145 t.Error("expected error with nil lattice")
146 }
147 }
148
149 func TestSignDoesNotMutateLattice(t *testing.T) {
150 l := buildMatureLattice()
151 params := DefaultParams(Security128)
152 kp := GenerateKeyPair(l, params, testFactory)
153
154 // Record original occupancy.
155 origOcc := occupiedCount(l)
156
157 // Sign multiple messages.
158 for _, msg := range []string{"msg1", "msg2", "msg3"} {
159 _, err := Sign(&kp.Private, []byte(msg), params)
160 if err != nil {
161 t.Fatalf("Sign(%q): %v", msg, err)
162 }
163 }
164
165 // Lattice must be unchanged.
166 if occupiedCount(l) != origOcc {
167 t.Errorf("lattice occupancy changed: was %d, now %d", origOcc, occupiedCount(l))
168 }
169 }
170
171 func TestSignMultipleAllVerify(t *testing.T) {
172 l := buildMatureLattice()
173 params := DefaultParams(Security128)
174 kp := GenerateKeyPair(l, params, testFactory)
175 fp := FingerprintFromSpore(kp.Public.Spore)
176
177 messages := []string{
178 "first", "second", "third", "fourth", "fifth",
179 "sixth", "seventh", "eighth", "ninth", "tenth",
180 }
181 for _, msg := range messages {
182 sig, err := Sign(&kp.Private, []byte(msg), params)
183 if err != nil {
184 t.Fatalf("Sign(%q): %v", msg, err)
185 }
186 if !Verify(fp, []byte(msg), sig) {
187 t.Errorf("signature for %q did not verify", msg)
188 }
189 }
190 }
191
192 func TestVerifyStructuralChecks(t *testing.T) {
193 l := buildMatureLattice()
194 params := DefaultParams(Security128)
195 kp := GenerateKeyPair(l, params, testFactory)
196
197 msg := []byte("structural test")
198 sig, err := Sign(&kp.Private, msg, params)
199 if err != nil {
200 t.Fatalf("Sign: %v", err)
201 }
202
203 fp := FingerprintFromSpore(kp.Public.Spore)
204
205 // Sanity: valid signature verifies.
206 if !Verify(fp, msg, sig) {
207 t.Fatal("valid signature should verify")
208 }
209
210 // Proof length mismatch: truncate LockIns.
211 t.Run("truncated_lockins", func(t *testing.T) {
212 bad := copySig(sig)
213 if len(bad.Proof.LockIns) > 1 {
214 bad.Proof.LockIns = bad.Proof.LockIns[:1]
215 }
216 if Verify(fp, msg, bad) {
217 t.Error("truncated lock-ins should not verify")
218 }
219 })
220
221 // Proof length mismatch: truncate NeighborCounts.
222 t.Run("truncated_neighbors", func(t *testing.T) {
223 bad := copySig(sig)
224 if len(bad.Proof.NeighborCounts) > 1 {
225 bad.Proof.NeighborCounts = bad.Proof.NeighborCounts[:1]
226 }
227 if Verify(fp, msg, bad) {
228 t.Error("truncated neighbor counts should not verify")
229 }
230 })
231
232 // Zero neighbor count at a bonded site.
233 t.Run("zero_neighbor", func(t *testing.T) {
234 bad := copySig(sig)
235 if len(bad.Proof.NeighborCounts) > 0 {
236 bad.Proof.NeighborCounts[0] = 0
237 }
238 if Verify(fp, msg, bad) {
239 t.Error("zero neighbor count should not verify")
240 }
241 })
242 }
243
244 // copySig returns a shallow copy of a Signature with independent proof slices.
245 func copySig(s *Signature) *Signature {
246 cp := *s
247 cp.Proof.LockIns = append([]ratio.Ratio(nil), s.Proof.LockIns...)
248 cp.Proof.NeighborCounts = append([]int(nil), s.Proof.NeighborCounts...)
249 cp.Proof.HexTrace = append([]state.Hexagram(nil), s.Proof.HexTrace...)
250 cp.Response = append([]SiteMark(nil), s.Response...)
251 return &cp
252 }
253