diff --git a/README.md b/README.md index c59a962..2b2743b 100644 --- a/README.md +++ b/README.md @@ -96,7 +96,7 @@ event := events.Event{ } // 2. Compute the event ID -id, err := event.GetID() +id, err := events.GetID(event) if err != nil { log.Fatal(err) } @@ -114,7 +114,7 @@ event.Sig = sig ```go // Returns canonical JSON: [0, pubkey, created_at, kind, tags, content] -serialized, err := event.Serialize() +serialized, err := events.Serialize(event) if err != nil { log.Fatal(err) } @@ -123,7 +123,7 @@ if err != nil { #### Compute event ID manually ```go -id, err := event.GetID() +id, err := events.GetID(event) if err != nil { log.Fatal(err) } @@ -138,7 +138,7 @@ if err != nil { ```go // Checks structure, ID computation, and signature -if err := event.Validate(); err != nil { +if err := events.Validate(event); err != nil { log.Printf("Invalid event: %v", err) } ``` @@ -147,17 +147,17 @@ if err := event.Validate(); err != nil { ```go // Check field formats and lengths -if err := event.ValidateStructure(); err != nil { +if err := events.ValidateStructure(event); err != nil { log.Printf("Malformed structure: %v", err) } // Verify ID matches computed hash -if err := event.ValidateID(); err != nil { +if err := events.ValidateID(event); err != nil { log.Printf("ID mismatch: %v", err) } // Verify cryptographic signature -if err := event.ValidateSignature(); err != nil { +if err := events.ValidateSignature(event); err != nil { log.Printf("Invalid signature: %v", err) } ``` @@ -186,7 +186,7 @@ if err != nil { } // Validate after unmarshaling -if err := event.Validate(); err != nil { +if err := events.Validate(event); err != nil { log.Printf("Received invalid event: %v", err) } ``` @@ -250,7 +250,7 @@ filter := filters.Filter{ Kinds: []int{1}, } -if filter.Matches(&event) { +if filters.Matches(filter, event) { // Event satisfies all filter conditions } ``` @@ -269,7 +269,7 @@ filter := filters.Filter{ var matches []events.Event for _, event := range events { - if filter.Matches(&event) { + if filters.Matches(filter, event) { matches = append(matches, event) } } @@ -293,7 +293,7 @@ filter := filters.Filter{ }, } -jsonBytes, err := filter.MarshalJSON() +jsonBytes, err := filters.MarshalJSON(filter) // Result: {"ids":["abc123"],"kinds":[1],"#e":["event-id"],"search":"nostr"} ``` @@ -309,7 +309,7 @@ jsonData := `{ }` var filter filters.Filter -err := filter.UnmarshalJSON([]byte(jsonData)) +err := filters.UnmarshalJSON([]byte(jsonData), &filter) if err != nil { log.Fatal(err) } diff --git a/events/event_json_test.go b/events/event_json_test.go index 121b372..016bbca 100644 --- a/events/event_json_test.go +++ b/events/event_json_test.go @@ -9,7 +9,7 @@ import ( func TestUnmarshalEventJSON(t *testing.T) { event := Event{} json.Unmarshal(testEventJSONBytes, &event) - if err := event.Validate(); err != nil { + if err := Validate(event); err != nil { t.Error("unmarshalled event is invalid") } expectEqualEvents(t, event, testEvent) @@ -37,7 +37,7 @@ func TestEventJSONRoundTrip(t *testing.T) { } expectedJSON := `{"id":"86e856d0527dd08527498cd8afd8a7d296bde37e4757a8921f034f0b344df3ad","pubkey":"cfa87f35acbde29ba1ab3ee42de527b2cad33ac487e80cf2d6405ea0042c8fef","created_at":1760740551,"kind":1,"tags":[["a","value"],["b","value","optional"],["name","value","optional","optional"]],"content":"hello world","sig":"c05fe02a9c082ff56aad2b16b5347498a21665f02f050ba086dbe6bd593c8cd448505d2831d1c0340acc1793eaf89b7c0cb21bb696c71da6b8d6b857702bb557"}` - if err := event.Validate(); err != nil { + if err := Validate(event); err != nil { t.Error("test event is invalid") } eventJSON, err := json.Marshal(event) @@ -47,7 +47,7 @@ func TestEventJSONRoundTrip(t *testing.T) { unmarshalledEvent := Event{} json.Unmarshal(eventJSON, &unmarshalledEvent) - if err := unmarshalledEvent.Validate(); err != nil { + if err := Validate(unmarshalledEvent); err != nil { t.Error("unmarshalled event is invalid") } expectEqualEvents(t, unmarshalledEvent, event) diff --git a/events/id.go b/events/id.go index f2aebc8..d0cd744 100644 --- a/events/id.go +++ b/events/id.go @@ -8,7 +8,7 @@ import ( // Serialize returns the canonical JSON array representation of the event. // used for ID computation: [0, pubkey, created_at, kind, tags, content]. -func (e *Event) Serialize() ([]byte, error) { +func Serialize(e Event) ([]byte, error) { serialized := []interface{}{ 0, e.PubKey, @@ -27,8 +27,8 @@ func (e *Event) Serialize() ([]byte, error) { // GetID computes and returns the event ID as a lowercase, hex-encoded SHA-256 hash // of the serialized event. -func (e *Event) GetID() (string, error) { - bytes, err := e.Serialize() +func GetID(e Event) (string, error) { + bytes, err := Serialize(e) if err != nil { return "", err } diff --git a/events/id_test.go b/events/id_test.go index 4475d3f..a72bbb2 100644 --- a/events/id_test.go +++ b/events/id_test.go @@ -196,7 +196,7 @@ var idTestCases = []IDTestCase{ func TestEventGetId(t *testing.T) { for _, tc := range idTestCases { t.Run(tc.name, func(t *testing.T) { - actual, err := tc.event.GetID() + actual, err := GetID(tc.event) assert.NoError(t, err) assert.Equal(t, tc.expected, actual) }) diff --git a/events/validate.go b/events/validate.go index a44afab..be7fd45 100644 --- a/events/validate.go +++ b/events/validate.go @@ -9,21 +9,21 @@ import ( // Validate performs a complete event validation: structure, ID computation, // and signature verification. Returns the first error encountered. -func (e *Event) Validate() error { - if err := e.ValidateStructure(); err != nil { +func Validate(e Event) error { + if err := ValidateStructure(e); err != nil { return err } - if err := e.ValidateID(); err != nil { + if err := ValidateID(e); err != nil { return err } - return e.ValidateSignature() + return ValidateSignature(e) } // ValidateStructure checks that all event fields conform to the protocol // specification: hex lengths, tag structure, and field formats. -func (e *Event) ValidateStructure() error { +func ValidateStructure(e Event) error { if !Hex64Pattern.MatchString(e.PubKey) { return errors.MalformedPubKey } @@ -46,8 +46,8 @@ func (e *Event) ValidateStructure() error { } // ValidateID recomputes the event ID and verifies it matches the stored ID field. -func (e *Event) ValidateID() error { - computedID, err := e.GetID() +func ValidateID(e Event) error { + computedID, err := GetID(e) if err != nil { return errors.FailedIDComp } @@ -62,7 +62,7 @@ func (e *Event) ValidateID() error { // ValidateSignature verifies the event signature is cryptographically valid // for the event ID and public key using Schnorr verification. -func (e *Event) ValidateSignature() error { +func ValidateSignature(e Event) error { idBytes, err := hex.DecodeString(e.ID) if err != nil { return fmt.Errorf("invalid event id hex: %w", err) diff --git a/events/validate_test.go b/events/validate_test.go index 75bf19c..7097983 100644 --- a/events/validate_test.go +++ b/events/validate_test.go @@ -184,7 +184,7 @@ var structureTestCases = []ValidateEventTestCase{ func TestValidateEventStructure(t *testing.T) { for _, tc := range structureTestCases { t.Run(tc.name, func(t *testing.T) { - err := tc.event.ValidateStructure() + err := ValidateStructure(tc.event) assert.ErrorContains(t, err, tc.expectedError) }) } @@ -201,7 +201,7 @@ func TestValidateEventIDFailure(t *testing.T) { Sig: testEvent.Sig, } - err := event.ValidateID() + err := ValidateID(event) assert.ErrorContains(t, err, "does not match computed id") } @@ -211,7 +211,7 @@ func TestValidateSignature(t *testing.T) { PubKey: testEvent.PubKey, Sig: testEvent.Sig, } - err := event.ValidateSignature() + err := ValidateSignature(event) assert.NoError(t, err) } @@ -222,7 +222,7 @@ func TestValidateInvalidSignature(t *testing.T) { PubKey: testEvent.PubKey, Sig: "9e43cbcf7e828a21c53fa35371ee79bffbfd7a3063ae46fc05ec623dd3186667c57e3d006488015e19247df35eb41c61013e051aa87860e23fa5ffbd44120482", } - err := event.ValidateSignature() + err := ValidateSignature(event) assert.ErrorContains(t, err, "event signature is invalid") } @@ -281,7 +281,7 @@ func TestValidateSignatureInvalidEventSignature(t *testing.T) { for _, tc := range validateSignatureTestCases { t.Run(tc.name, func(t *testing.T) { event := Event{ID: tc.id, PubKey: tc.pubkey, Sig: tc.sig} - err := event.ValidateSignature() + err := ValidateSignature(event) assert.ErrorContains(t, err, tc.expectedError) }) } @@ -301,6 +301,6 @@ func TestValidateEvent(t *testing.T) { Sig: "668a715f1eb983172acf230d17bd283daedb2598adf8de4290bcc7eb0b802fdb60669d1e7d1104ac70393f4dbccd07e8abf897152af6ce6c0a75499874e27f14", } - err := event.Validate() + err := Validate(event) assert.NoError(t, err) } diff --git a/filters/filter.go b/filters/filter.go index 8031950..e5384e2 100644 --- a/filters/filter.go +++ b/filters/filter.go @@ -29,7 +29,7 @@ type Filter struct { // MarshalJSON converts the filter to JSON with standard fields, tag filters // (prefixed with "#"), and extensions merged into a single object. -func (f *Filter) MarshalJSON() ([]byte, error) { +func MarshalJSON(f Filter) ([]byte, error) { outputMap := make(map[string]interface{}) // Add standard fields @@ -86,7 +86,7 @@ func (f *Filter) MarshalJSON() ([]byte, error) { // UnmarshalJSON parses JSON into the filter, separating standard fields, // tag filters (keys starting with "#"), and extensions. -func (f *Filter) UnmarshalJSON(data []byte) error { +func UnmarshalJSON(data []byte, f *Filter) error { // Decode into raw map raw := make(FilterExtensions) if err := json.Unmarshal(data, &raw); err != nil { @@ -182,7 +182,7 @@ func (f *Filter) UnmarshalJSON(data []byte) error { // Matches returns true if the event satisfies all filter conditions. // Supports prefix matching for IDs and authors, and tag filtering. // Does not account for custom extensions. -func (f *Filter) Matches(event *events.Event) bool { +func Matches(f Filter, event events.Event) bool { // Check ID if len(f.IDs) > 0 { if !matchesPrefix(event.ID, f.IDs) { diff --git a/filters/filter_json_test.go b/filters/filter_json_test.go index ae27d4d..1976e11 100644 --- a/filters/filter_json_test.go +++ b/filters/filter_json_test.go @@ -584,7 +584,7 @@ var roundTripTestCases = []FilterRoundTripTestCase{ func TestFilterMarshalJSON(t *testing.T) { for _, tc := range marshalTestCases { t.Run(tc.name, func(t *testing.T) { - result, err := tc.filter.MarshalJSON() + result, err := MarshalJSON(tc.filter) assert.NoError(t, err) var expectedMap, actualMap map[string]interface{} @@ -602,7 +602,7 @@ func TestFilterUnmarshalJSON(t *testing.T) { for _, tc := range unmarshalTestCases { t.Run(tc.name, func(t *testing.T) { var result Filter - err := result.UnmarshalJSON([]byte(tc.input)) + err := UnmarshalJSON([]byte(tc.input), &result) assert.NoError(t, err) expectEqualFilters(t, result, tc.expected) @@ -613,11 +613,11 @@ func TestFilterUnmarshalJSON(t *testing.T) { func TestFilterRoundTrip(t *testing.T) { for _, tc := range roundTripTestCases { t.Run(tc.name, func(t *testing.T) { - jsonBytes, err := tc.filter.MarshalJSON() + jsonBytes, err := MarshalJSON(tc.filter) assert.NoError(t, err) var result Filter - err = result.UnmarshalJSON(jsonBytes) + err = UnmarshalJSON(jsonBytes, &result) assert.NoError(t, err) expectEqualFilters(t, result, tc.filter) diff --git a/filters/filter_match_test.go b/filters/filter_match_test.go index 227d8d6..bb8d23a 100644 --- a/filters/filter_match_test.go +++ b/filters/filter_match_test.go @@ -391,7 +391,7 @@ func TestEventFilterMatching(t *testing.T) { t.Run(tc.name, func(t *testing.T) { actualIDs := []string{} for _, event := range testEvents { - if tc.filter.Matches(&event) { + if Matches(tc.filter, event) { actualIDs = append(actualIDs, event.ID[:8]) } } @@ -416,5 +416,5 @@ func TestEventFilterMatchingSkipMalformedTags(t *testing.T) { }, } - assert.True(t, filter.Matches(&event)) + assert.True(t, Matches(filter, event)) }