package canon import ( "crypto/ed25519" "encoding/binary" "errors" ) const ( // ResponseDomainMarkerV1 binds the v1 server response signature to the // Galaxy gateway transport contract. ResponseDomainMarkerV1 = "galaxy-response-v1" ) // ErrInvalidResponseSignature reports that a server response signature is // not a raw Ed25519 signature for the canonical response signing input. var ErrInvalidResponseSignature = errors.New("invalid response signature") // ResponseSigningFields contains the canonical v1 response fields that are // bound into the server signing input. type ResponseSigningFields struct { // ProtocolVersion identifies the transport envelope version. ProtocolVersion string // RequestID is the transport correlation identifier copied from the // authenticated request. RequestID string // TimestampMS carries the server response timestamp in milliseconds. TimestampMS int64 // ResultCode is the opaque downstream result code returned to the client. ResultCode string // PayloadHash is the raw SHA-256 digest of response payload bytes. PayloadHash []byte } // BuildResponseSigningInput returns the canonical byte sequence the v1 server // response signature covers. String and byte fields are length-prefixed with // uvarint(len(field)) followed by raw bytes, while TimestampMS is appended as // an 8-byte big-endian uint64. func BuildResponseSigningInput(fields ResponseSigningFields) []byte { size := len(ResponseDomainMarkerV1) + len(fields.ProtocolVersion) + len(fields.RequestID) + len(fields.ResultCode) + len(fields.PayloadHash) + (5 * binary.MaxVarintLen64) + 8 buf := make([]byte, 0, size) buf = appendLengthPrefixedString(buf, ResponseDomainMarkerV1) buf = appendLengthPrefixedString(buf, fields.ProtocolVersion) buf = appendLengthPrefixedString(buf, fields.RequestID) buf = binary.BigEndian.AppendUint64(buf, uint64(fields.TimestampMS)) buf = appendLengthPrefixedString(buf, fields.ResultCode) buf = appendLengthPrefixedBytes(buf, fields.PayloadHash) return buf } // VerifyResponseSignature verifies that signature authenticates fields under // publicKey using the canonical v1 response signing input. func VerifyResponseSignature(publicKey ed25519.PublicKey, signature []byte, fields ResponseSigningFields) error { if len(publicKey) != ed25519.PublicKeySize || len(signature) != ed25519.SignatureSize { return ErrInvalidResponseSignature } if !ed25519.Verify(publicKey, BuildResponseSigningInput(fields), signature) { return ErrInvalidResponseSignature } return nil }