ephemeral_wire_test.go raw
1 package crypto
2
3 import (
4 "testing"
5 )
6
7 func TestEphemeralMarshalRoundTrip(t *testing.T) {
8 params := DefaultParams(Security128)
9 tags := []string{"word", "punct"}
10
11 kpAlice, err := Generate(params, tags, testFactory)
12 if err != nil {
13 t.Fatalf("Generate(Alice): %v", err)
14 }
15 kpBob, err := Generate(params, tags, testFactory)
16 if err != nil {
17 t.Fatalf("Generate(Bob): %v", err)
18 }
19
20 msg, err := PrepareExchange(
21 &kpAlice.Private, kpAlice.Public.Spore, kpBob.Public.Spore, params,
22 )
23 if err != nil {
24 t.Fatalf("PrepareExchange: %v", err)
25 }
26
27 // Marshal.
28 data, err := msg.MarshalEphemeral(params.N)
29 if err != nil {
30 t.Fatalf("MarshalEphemeral: %v", err)
31 }
32
33 if len(data) == 0 {
34 t.Fatal("marshalled data should not be empty")
35 }
36
37 // Unmarshal.
38 recovered, err := UnmarshalEphemeralMessage(data)
39 if err != nil {
40 t.Fatalf("UnmarshalEphemeralMessage: %v", err)
41 }
42
43 // Verify signature preserved.
44 if recovered.Signature == nil {
45 t.Fatal("recovered signature should not be nil")
46 }
47 // V2 stores only the first 16 bytes of the challenge (truncated binding).
48 for i := range 16 {
49 if recovered.Signature.Challenge[i] != msg.Signature.Challenge[i] {
50 t.Errorf("challenge byte %d: got %02x, want %02x",
51 i, recovered.Signature.Challenge[i], msg.Signature.Challenge[i])
52 }
53 }
54 // Commitment is not preserved in V2 (recomputed by verifier).
55
56 // Verify pattern length preserved.
57 if len(recovered.Pattern) != len(msg.Pattern) {
58 t.Errorf("pattern length = %d, want %d", len(recovered.Pattern), len(msg.Pattern))
59 }
60
61 // Count occupied sites in both.
62 origOcc := 0
63 for _, s := range msg.Pattern {
64 if s.Occupied {
65 origOcc++
66 }
67 }
68 recOcc := 0
69 for _, s := range recovered.Pattern {
70 if s.Occupied {
71 recOcc++
72 }
73 }
74 if recOcc != origOcc {
75 t.Errorf("occupied count = %d, want %d", recOcc, origOcc)
76 }
77
78 // Verify occupied sites have correct tags and projections.
79 for i, s := range msg.Pattern {
80 if !s.Occupied {
81 continue
82 }
83 r := recovered.Pattern[i]
84 if !r.Occupied {
85 t.Errorf("site %d: should be occupied", i)
86 continue
87 }
88 if r.TypeTag != s.TypeTag {
89 t.Errorf("site %d: tag = %q, want %q", i, r.TypeTag, s.TypeTag)
90 }
91 if r.Projection != s.Projection {
92 t.Errorf("site %d: projection = %d, want %d", i, r.Projection, s.Projection)
93 }
94 if r.Perm != s.Perm {
95 t.Errorf("site %d: perm = %d, want %d", i, r.Perm, s.Perm)
96 }
97 }
98
99 // Verify sender fingerprint PermDist preserved.
100 for i := range 6 {
101 if recovered.SenderFingerprint.PermDist[i] != msg.SenderFingerprint.PermDist[i] {
102 t.Errorf("PermDist[%d] = %d, want %d",
103 i, recovered.SenderFingerprint.PermDist[i], msg.SenderFingerprint.PermDist[i])
104 }
105 }
106 }
107
108 func TestEphemeralMarshalNil(t *testing.T) {
109 var msg *EphemeralMessage
110 _, err := msg.MarshalEphemeral(256)
111 if err == nil {
112 t.Error("expected error for nil message")
113 }
114 }
115
116 func TestEphemeralUnmarshalTruncated(t *testing.T) {
117 _, err := UnmarshalEphemeralMessage([]byte{0x01})
118 if err == nil {
119 t.Error("expected error for truncated data")
120 }
121 }
122
123 func TestEphemeralUnmarshalBadVersion(t *testing.T) {
124 data := make([]byte, 100)
125 data[0] = 0xFF // bad version
126 data[1] = 0xFF
127 _, err := UnmarshalEphemeralMessage(data)
128 if err == nil {
129 t.Error("expected error for bad version")
130 }
131 }
132
133 func TestEphemeralMarshalDeterministic(t *testing.T) {
134 params := DefaultParams(Security128)
135 tags := []string{"word", "punct"}
136
137 kpAlice, _ := Generate(params, tags, testFactory)
138 kpBob, _ := Generate(params, tags, testFactory)
139
140 msg, _ := PrepareExchange(
141 &kpAlice.Private, kpAlice.Public.Spore, kpBob.Public.Spore, params,
142 )
143
144 data1, _ := msg.MarshalEphemeral(params.N)
145 data2, _ := msg.MarshalEphemeral(params.N)
146
147 if len(data1) != len(data2) {
148 t.Errorf("marshal lengths differ: %d vs %d", len(data1), len(data2))
149 }
150 for i := range data1 {
151 if data1[i] != data2[i] {
152 t.Errorf("marshal differs at byte %d: %02x vs %02x", i, data1[i], data2[i])
153 break
154 }
155 }
156 }
157
158 func TestEphemeralMarshalPreservesSignatureIntegrity(t *testing.T) {
159 // Verify that a marshalled+unmarshalled message preserves the
160 // signature's internal consistency (challenge, commitment, response).
161 // CompleteExchange uses the peer's known SporeFingerprint from their
162 // spore (not from the wire message), so full exchange round-trip
163 // verification is a protocol-level test, not a wire format test.
164 params := DefaultParams(Security128)
165 tags := []string{"word", "punct"}
166
167 kpAlice, _ := Generate(params, tags, testFactory)
168 kpBob, _ := Generate(params, tags, testFactory)
169
170 msg, _ := PrepareExchange(
171 &kpAlice.Private, kpAlice.Public.Spore, kpBob.Public.Spore, params,
172 )
173
174 data, err := msg.MarshalEphemeral(params.N)
175 if err != nil {
176 t.Fatalf("marshal: %v", err)
177 }
178
179 recovered, err := UnmarshalEphemeralMessage(data)
180 if err != nil {
181 t.Fatalf("unmarshal: %v", err)
182 }
183
184 // V2: truncated challenge (first 16 bytes) must be preserved.
185 for i := range 16 {
186 if recovered.Signature.Challenge[i] != msg.Signature.Challenge[i] {
187 t.Errorf("challenge byte %d not preserved", i)
188 }
189 }
190 // Commitment is not preserved in V2 format.
191
192 // Response occupied count must match.
193 origOcc := 0
194 for _, s := range msg.Signature.Response {
195 if s.Occupied {
196 origOcc++
197 }
198 }
199 recOcc := len(recovered.Signature.Response)
200 if recOcc != origOcc {
201 t.Errorf("signature response occupied = %d, want %d", recOcc, origOcc)
202 }
203
204 // V2 proof lengths must match response count.
205 if len(recovered.Signature.Proof.LockIns) != recOcc {
206 t.Errorf("proof lock-ins length = %d, want %d",
207 len(recovered.Signature.Proof.LockIns), recOcc)
208 }
209 }
210