validation.go raw

   1  // Package validation provides event validation services for the ORLY relay.
   2  // It handles structural validation (hex case, JSON format), cryptographic
   3  // validation (signature, ID), and protocol validation (timestamp, NIP-70).
   4  package validation
   5  
   6  import (
   7  	"next.orly.dev/pkg/nostr/encoders/event"
   8  )
   9  
  10  // ReasonCode identifies the type of validation failure for response formatting.
  11  type ReasonCode int
  12  
  13  const (
  14  	ReasonNone ReasonCode = iota
  15  	ReasonBlocked
  16  	ReasonInvalid
  17  	ReasonError
  18  )
  19  
  20  // Result contains the outcome of a validation check.
  21  type Result struct {
  22  	Valid  bool
  23  	Code   ReasonCode // For response formatting
  24  	Msg    string     // Human-readable error message
  25  }
  26  
  27  // OK returns a successful validation result.
  28  func OK() Result {
  29  	return Result{Valid: true}
  30  }
  31  
  32  // Blocked returns a blocked validation result.
  33  func Blocked(msg string) Result {
  34  	return Result{Valid: false, Code: ReasonBlocked, Msg: msg}
  35  }
  36  
  37  // Invalid returns an invalid validation result.
  38  func Invalid(msg string) Result {
  39  	return Result{Valid: false, Code: ReasonInvalid, Msg: msg}
  40  }
  41  
  42  // Error returns an error validation result.
  43  func Error(msg string) Result {
  44  	return Result{Valid: false, Code: ReasonError, Msg: msg}
  45  }
  46  
  47  // Validator validates events before processing.
  48  type Validator interface {
  49  	// ValidateRawJSON validates raw message before unmarshaling.
  50  	// This catches issues like uppercase hex that are lost after unmarshal.
  51  	ValidateRawJSON(msg []byte) Result
  52  
  53  	// ValidateEvent validates an unmarshaled event.
  54  	// Checks ID computation, signature, and timestamp.
  55  	ValidateEvent(ev *event.E) Result
  56  
  57  	// ValidateProtectedTag checks NIP-70 protected tag requirements.
  58  	// The authedPubkey is the authenticated pubkey of the connection.
  59  	ValidateProtectedTag(ev *event.E, authedPubkey []byte) Result
  60  }
  61  
  62  // Config holds configuration for the validation service.
  63  type Config struct {
  64  	// MaxFutureSeconds is how far in the future a timestamp can be (default: 3600 = 1 hour)
  65  	MaxFutureSeconds int64
  66  }
  67  
  68  // DefaultConfig returns the default validation configuration.
  69  func DefaultConfig() *Config {
  70  	return &Config{
  71  		MaxFutureSeconds: 3600,
  72  	}
  73  }
  74  
  75  // Service implements the Validator interface.
  76  type Service struct {
  77  	cfg *Config
  78  }
  79  
  80  // New creates a new validation service with default configuration.
  81  func New() *Service {
  82  	return &Service{cfg: DefaultConfig()}
  83  }
  84  
  85  // NewWithConfig creates a new validation service with the given configuration.
  86  func NewWithConfig(cfg *Config) *Service {
  87  	if cfg == nil {
  88  		cfg = DefaultConfig()
  89  	}
  90  	return &Service{cfg: cfg}
  91  }
  92  
  93  // ValidateRawJSON validates raw message before unmarshaling.
  94  func (s *Service) ValidateRawJSON(msg []byte) Result {
  95  	if errMsg := ValidateLowercaseHexInJSON(msg); errMsg != "" {
  96  		return Blocked(errMsg)
  97  	}
  98  	return OK()
  99  }
 100  
 101  // ValidateEvent validates an unmarshaled event.
 102  func (s *Service) ValidateEvent(ev *event.E) Result {
 103  	// Validate event ID
 104  	if result := ValidateEventID(ev); !result.Valid {
 105  		return result
 106  	}
 107  
 108  	// Validate timestamp
 109  	if result := ValidateTimestamp(ev, s.cfg.MaxFutureSeconds); !result.Valid {
 110  		return result
 111  	}
 112  
 113  	// Validate signature
 114  	if result := ValidateSignature(ev); !result.Valid {
 115  		return result
 116  	}
 117  
 118  	return OK()
 119  }
 120  
 121  // ValidateProtectedTag checks NIP-70 protected tag requirements.
 122  func (s *Service) ValidateProtectedTag(ev *event.E, authedPubkey []byte) Result {
 123  	return ValidateProtectedTagMatch(ev, authedPubkey)
 124  }
 125