// Package validation provides event validation services for the ORLY relay. // It handles structural validation (hex case, JSON format), cryptographic // validation (signature, ID), and protocol validation (timestamp, NIP-70). package validation import ( "git.mleku.dev/mleku/nostr/encoders/event" ) // ReasonCode identifies the type of validation failure for response formatting. type ReasonCode int const ( ReasonNone ReasonCode = iota ReasonBlocked ReasonInvalid ReasonError ) // Result contains the outcome of a validation check. type Result struct { Valid bool Code ReasonCode // For response formatting Msg string // Human-readable error message } // OK returns a successful validation result. func OK() Result { return Result{Valid: true} } // Blocked returns a blocked validation result. func Blocked(msg string) Result { return Result{Valid: false, Code: ReasonBlocked, Msg: msg} } // Invalid returns an invalid validation result. func Invalid(msg string) Result { return Result{Valid: false, Code: ReasonInvalid, Msg: msg} } // Error returns an error validation result. func Error(msg string) Result { return Result{Valid: false, Code: ReasonError, Msg: msg} } // Validator validates events before processing. type Validator interface { // ValidateRawJSON validates raw message before unmarshaling. // This catches issues like uppercase hex that are lost after unmarshal. ValidateRawJSON(msg []byte) Result // ValidateEvent validates an unmarshaled event. // Checks ID computation, signature, and timestamp. ValidateEvent(ev *event.E) Result // ValidateProtectedTag checks NIP-70 protected tag requirements. // The authedPubkey is the authenticated pubkey of the connection. ValidateProtectedTag(ev *event.E, authedPubkey []byte) Result } // Config holds configuration for the validation service. type Config struct { // MaxFutureSeconds is how far in the future a timestamp can be (default: 3600 = 1 hour) MaxFutureSeconds int64 } // DefaultConfig returns the default validation configuration. func DefaultConfig() *Config { return &Config{ MaxFutureSeconds: 3600, } } // Service implements the Validator interface. type Service struct { cfg *Config } // New creates a new validation service with default configuration. func New() *Service { return &Service{cfg: DefaultConfig()} } // NewWithConfig creates a new validation service with the given configuration. func NewWithConfig(cfg *Config) *Service { if cfg == nil { cfg = DefaultConfig() } return &Service{cfg: cfg} } // ValidateRawJSON validates raw message before unmarshaling. func (s *Service) ValidateRawJSON(msg []byte) Result { if errMsg := ValidateLowercaseHexInJSON(msg); errMsg != "" { return Blocked(errMsg) } return OK() } // ValidateEvent validates an unmarshaled event. func (s *Service) ValidateEvent(ev *event.E) Result { // Validate event ID if result := ValidateEventID(ev); !result.Valid { return result } // Validate timestamp if result := ValidateTimestamp(ev, s.cfg.MaxFutureSeconds); !result.Valid { return result } // Validate signature if result := ValidateSignature(ev); !result.Valid { return result } return OK() } // ValidateProtectedTag checks NIP-70 protected tag requirements. func (s *Service) ValidateProtectedTag(ev *event.E, authedPubkey []byte) Result { return ValidateProtectedTagMatch(ev, authedPubkey) }