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