|
1 | 1 | package envelope |
2 | 2 |
|
3 | 3 | import ( |
| 4 | + "bytes" |
4 | 5 | "testing" |
| 6 | + "time" |
5 | 7 |
|
| 8 | + "github.com/peerclaw/peerclaw-core/identity" |
6 | 9 | "github.com/peerclaw/peerclaw-core/protocol" |
7 | 10 | ) |
8 | 11 |
|
@@ -75,3 +78,85 @@ func TestNewResponse_SetsPayload(t *testing.T) { |
75 | 78 | t.Errorf("expected Payload world, got %s", string(resp.Payload)) |
76 | 79 | } |
77 | 80 | } |
| 81 | + |
| 82 | +func TestSigningPayload_Deterministic(t *testing.T) { |
| 83 | + env := &Envelope{ |
| 84 | + Source: "alice", |
| 85 | + Destination: "bob", |
| 86 | + Protocol: protocol.ProtocolA2A, |
| 87 | + MessageType: MessageTypeRequest, |
| 88 | + Nonce: "nonce-123", |
| 89 | + Timestamp: time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC), |
| 90 | + Payload: []byte("hello"), |
| 91 | + } |
| 92 | + |
| 93 | + p1 := env.SigningPayload() |
| 94 | + p2 := env.SigningPayload() |
| 95 | + if !bytes.Equal(p1, p2) { |
| 96 | + t.Error("SigningPayload should be deterministic") |
| 97 | + } |
| 98 | +} |
| 99 | + |
| 100 | +func TestSigningPayload_ChangesOnFieldMutation(t *testing.T) { |
| 101 | + env := &Envelope{ |
| 102 | + Source: "alice", |
| 103 | + Destination: "bob", |
| 104 | + Protocol: protocol.ProtocolA2A, |
| 105 | + MessageType: MessageTypeRequest, |
| 106 | + Nonce: "nonce-123", |
| 107 | + Timestamp: time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC), |
| 108 | + Payload: []byte("hello"), |
| 109 | + } |
| 110 | + |
| 111 | + baseline := env.SigningPayload() |
| 112 | + |
| 113 | + // Changing any covered field should produce a different signing payload. |
| 114 | + fields := []struct { |
| 115 | + name string |
| 116 | + mutate func() |
| 117 | + revert func() |
| 118 | + }{ |
| 119 | + {"Source", func() { env.Source = "mallory" }, func() { env.Source = "alice" }}, |
| 120 | + {"Destination", func() { env.Destination = "eve" }, func() { env.Destination = "bob" }}, |
| 121 | + {"Protocol", func() { env.Protocol = protocol.ProtocolMCP }, func() { env.Protocol = protocol.ProtocolA2A }}, |
| 122 | + {"MessageType", func() { env.MessageType = MessageTypeResponse }, func() { env.MessageType = MessageTypeRequest }}, |
| 123 | + {"Nonce", func() { env.Nonce = "other" }, func() { env.Nonce = "nonce-123" }}, |
| 124 | + {"Timestamp", func() { env.Timestamp = time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC) }, func() { env.Timestamp = time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC) }}, |
| 125 | + {"Payload", func() { env.Payload = []byte("tampered") }, func() { env.Payload = []byte("hello") }}, |
| 126 | + } |
| 127 | + |
| 128 | + for _, f := range fields { |
| 129 | + f.mutate() |
| 130 | + changed := env.SigningPayload() |
| 131 | + if bytes.Equal(baseline, changed) { |
| 132 | + t.Errorf("changing %s should produce different signing payload", f.name) |
| 133 | + } |
| 134 | + f.revert() |
| 135 | + } |
| 136 | +} |
| 137 | + |
| 138 | +func TestSignEnvelope_VerifyEnvelope_RoundTrip(t *testing.T) { |
| 139 | + kp, err := identity.GenerateKeypair() |
| 140 | + if err != nil { |
| 141 | + t.Fatalf("GenerateKeypair: %v", err) |
| 142 | + } |
| 143 | + |
| 144 | + env := New("alice", "bob", protocol.ProtocolA2A, []byte("hello")) |
| 145 | + env.Nonce = "test-nonce" |
| 146 | + identity.SignEnvelope(env, kp.PrivateKey) |
| 147 | + |
| 148 | + if env.Signature == "" { |
| 149 | + t.Fatal("expected non-empty signature") |
| 150 | + } |
| 151 | + |
| 152 | + pubKey, _ := identity.ParsePublicKey(kp.PublicKeyString()) |
| 153 | + if err := identity.VerifyEnvelope(env, pubKey); err != nil { |
| 154 | + t.Fatalf("VerifyEnvelope should pass: %v", err) |
| 155 | + } |
| 156 | + |
| 157 | + // Tamper with Source — should fail. |
| 158 | + env.Source = "mallory" |
| 159 | + if err := identity.VerifyEnvelope(env, pubKey); err == nil { |
| 160 | + t.Error("tampered Source should fail verification") |
| 161 | + } |
| 162 | +} |
0 commit comments