package canon_test import ( "crypto/ed25519" "crypto/sha256" "encoding/base64" "testing" "galaxy/core/canon" "github.com/stretchr/testify/require" ) func TestVerifyRequestSignature(t *testing.T) { t.Parallel() clientPrivateKey := newTestPrivateKey("primary") clientPublicKey, _ := clientPrivateKey.Public().(ed25519.PublicKey) otherPrivateKey := newTestPrivateKey("other") otherPublicKey, _ := otherPrivateKey.Public().(ed25519.PublicKey) fields := canon.RequestSigningFields{ ProtocolVersion: "v1", DeviceSessionID: "device-session-123", MessageType: "user.games.command", TimestampMS: 123456789, RequestID: "request-123", PayloadHash: sha256Sum([]byte("payload")), } signature := ed25519.Sign(clientPrivateKey, canon.BuildRequestSigningInput(fields)) tests := []struct { name string clientPublicKey string signature []byte fields canon.RequestSigningFields wantErr error }{ { name: "valid signature", clientPublicKey: base64.StdEncoding.EncodeToString(clientPublicKey), signature: signature, fields: fields, }, { name: "tampered payload hash rejects signature", clientPublicKey: base64.StdEncoding.EncodeToString(clientPublicKey), signature: signature, fields: func() canon.RequestSigningFields { mutated := fields mutated.PayloadHash = sha256Sum([]byte("other")) return mutated }(), wantErr: canon.ErrInvalidRequestSignature, }, { name: "mismatched request id rejects signature", clientPublicKey: base64.StdEncoding.EncodeToString(clientPublicKey), signature: signature, fields: func() canon.RequestSigningFields { mutated := fields mutated.RequestID = "request-456" return mutated }(), wantErr: canon.ErrInvalidRequestSignature, }, { name: "mismatched timestamp rejects signature", clientPublicKey: base64.StdEncoding.EncodeToString(clientPublicKey), signature: signature, fields: func() canon.RequestSigningFields { mutated := fields mutated.TimestampMS++ return mutated }(), wantErr: canon.ErrInvalidRequestSignature, }, { name: "wrong key rejects signature", clientPublicKey: base64.StdEncoding.EncodeToString(otherPublicKey), signature: signature, fields: fields, wantErr: canon.ErrInvalidRequestSignature, }, { name: "bit-flipped signature rejects", clientPublicKey: base64.StdEncoding.EncodeToString(clientPublicKey), signature: func() []byte { corrupted := append([]byte(nil), signature...) corrupted[0] ^= 0xFF return corrupted }(), fields: fields, wantErr: canon.ErrInvalidRequestSignature, }, { name: "invalid signature length rejects", clientPublicKey: base64.StdEncoding.EncodeToString(clientPublicKey), signature: signature[:len(signature)-1], fields: fields, wantErr: canon.ErrInvalidRequestSignature, }, { name: "empty signature rejects", clientPublicKey: base64.StdEncoding.EncodeToString(clientPublicKey), signature: nil, fields: fields, wantErr: canon.ErrInvalidRequestSignature, }, { name: "invalid base64 public key rejects", clientPublicKey: "%%%not-base64%%%", signature: signature, fields: fields, wantErr: canon.ErrInvalidClientPublicKey, }, { name: "invalid public key length rejects", clientPublicKey: base64.StdEncoding.EncodeToString([]byte("short")), signature: signature, fields: fields, wantErr: canon.ErrInvalidClientPublicKey, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Parallel() err := canon.VerifyRequestSignature(tt.clientPublicKey, tt.signature, tt.fields) if tt.wantErr == nil { require.NoError(t, err) return } require.ErrorIs(t, err, tt.wantErr) }) } } func newTestPrivateKey(label string) ed25519.PrivateKey { seed := sha256.Sum256([]byte("ui-core-canon-signature-test-" + label)) return ed25519.NewKeyFromSeed(seed[:]) }