- Updated the NIP-XX document to clarify terminology, replacing "attestations" with "acts" for consistency. - Enhanced the protocol by introducing new event kinds: Trust Act (Kind 39101) and Group Tag Act (Kind 39102), with detailed specifications for their structure and usage. - Modified the signature generation process to include the canonical WebSocket URL, ensuring proper binding and verification. - Improved validation mechanisms for identity tags and event replication requests, reinforcing security and integrity within the directory consensus protocol. - Added comprehensive documentation for new event types and their respective validation processes, ensuring clarity for developers and users. - Introduced new helper functions and structures to facilitate the creation and management of directory events and acts.
265 lines
6.7 KiB
Go
265 lines
6.7 KiB
Go
package directory
|
|
|
|
import (
|
|
"encoding/json"
|
|
|
|
"lol.mleku.dev/chk"
|
|
"lol.mleku.dev/errorf"
|
|
"next.orly.dev/pkg/encoders/event"
|
|
"next.orly.dev/pkg/encoders/tag"
|
|
)
|
|
|
|
// RelayIdentityContent represents the JSON content of a Relay Identity
|
|
// Announcement event (Kind 39100).
|
|
type RelayIdentityContent struct {
|
|
Name string `json:"name"`
|
|
Description string `json:"description,omitempty"`
|
|
Contact string `json:"contact,omitempty"`
|
|
}
|
|
|
|
// RelayIdentityAnnouncement represents a complete Relay Identity Announcement
|
|
// event with typed access to its components.
|
|
type RelayIdentityAnnouncement struct {
|
|
Event *event.E
|
|
Content *RelayIdentityContent
|
|
RelayURL string
|
|
SigningKey string
|
|
EncryptionKey string
|
|
Version string
|
|
NIP11URL string
|
|
}
|
|
|
|
// NewRelayIdentityAnnouncement creates a new Relay Identity Announcement event.
|
|
func NewRelayIdentityAnnouncement(
|
|
pubkey []byte,
|
|
name, description, contact string,
|
|
relayURL, signingKey, encryptionKey, version, nip11URL string,
|
|
) (ria *RelayIdentityAnnouncement, err error) {
|
|
|
|
// Validate required fields
|
|
if len(pubkey) != 32 {
|
|
return nil, errorf.E("pubkey must be 32 bytes")
|
|
}
|
|
if name == "" {
|
|
return nil, errorf.E("name is required")
|
|
}
|
|
if relayURL == "" {
|
|
return nil, errorf.E("relay URL is required")
|
|
}
|
|
if signingKey == "" {
|
|
return nil, errorf.E("signing key is required")
|
|
}
|
|
if encryptionKey == "" {
|
|
return nil, errorf.E("encryption key is required")
|
|
}
|
|
if version == "" {
|
|
version = "1" // Default version
|
|
}
|
|
if nip11URL == "" {
|
|
return nil, errorf.E("NIP-11 URL is required")
|
|
}
|
|
|
|
// Create content
|
|
content := &RelayIdentityContent{
|
|
Name: name,
|
|
Description: description,
|
|
Contact: contact,
|
|
}
|
|
|
|
// Marshal content to JSON
|
|
var contentBytes []byte
|
|
if contentBytes, err = json.Marshal(content); chk.E(err) {
|
|
return
|
|
}
|
|
|
|
// Create base event
|
|
ev := CreateBaseEvent(pubkey, RelayIdentityAnnouncementKind)
|
|
ev.Content = contentBytes
|
|
|
|
// Add required tags
|
|
ev.Tags.Append(tag.NewFromAny(string(DTag), "relay-identity"))
|
|
ev.Tags.Append(tag.NewFromAny(string(RelayTag), relayURL))
|
|
ev.Tags.Append(tag.NewFromAny(string(SigningKeyTag), signingKey))
|
|
ev.Tags.Append(tag.NewFromAny(string(EncryptionKeyTag), encryptionKey))
|
|
ev.Tags.Append(tag.NewFromAny(string(VersionTag), version))
|
|
ev.Tags.Append(tag.NewFromAny(string(NIP11URLTag), nip11URL))
|
|
|
|
ria = &RelayIdentityAnnouncement{
|
|
Event: ev,
|
|
Content: content,
|
|
RelayURL: relayURL,
|
|
SigningKey: signingKey,
|
|
EncryptionKey: encryptionKey,
|
|
Version: version,
|
|
NIP11URL: nip11URL,
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// ParseRelayIdentityAnnouncement parses an event into a RelayIdentityAnnouncement
|
|
// structure with validation.
|
|
func ParseRelayIdentityAnnouncement(ev *event.E) (ria *RelayIdentityAnnouncement, err error) {
|
|
if ev == nil {
|
|
return nil, errorf.E("event cannot be nil")
|
|
}
|
|
|
|
// Validate event kind
|
|
if ev.Kind != RelayIdentityAnnouncementKind.K {
|
|
return nil, errorf.E("invalid event kind: expected %d, got %d",
|
|
RelayIdentityAnnouncementKind.K, ev.Kind)
|
|
}
|
|
|
|
// Parse content
|
|
var content RelayIdentityContent
|
|
if len(ev.Content) > 0 {
|
|
if err = json.Unmarshal(ev.Content, &content); chk.E(err) {
|
|
return nil, errorf.E("failed to parse content: %w", err)
|
|
}
|
|
}
|
|
|
|
// Extract required tags
|
|
dTag := ev.Tags.GetFirst(DTag)
|
|
if dTag == nil || string(dTag.Value()) != "relay-identity" {
|
|
return nil, errorf.E("missing or invalid d tag")
|
|
}
|
|
|
|
relayTag := ev.Tags.GetFirst(RelayTag)
|
|
if relayTag == nil {
|
|
return nil, errorf.E("missing relay tag")
|
|
}
|
|
|
|
signingKeyTag := ev.Tags.GetFirst(SigningKeyTag)
|
|
if signingKeyTag == nil {
|
|
return nil, errorf.E("missing signing_key tag")
|
|
}
|
|
|
|
encryptionKeyTag := ev.Tags.GetFirst(EncryptionKeyTag)
|
|
if encryptionKeyTag == nil {
|
|
return nil, errorf.E("missing encryption_key tag")
|
|
}
|
|
|
|
versionTag := ev.Tags.GetFirst(VersionTag)
|
|
if versionTag == nil {
|
|
return nil, errorf.E("missing version tag")
|
|
}
|
|
|
|
nip11URLTag := ev.Tags.GetFirst(NIP11URLTag)
|
|
if nip11URLTag == nil {
|
|
return nil, errorf.E("missing nip11_url tag")
|
|
}
|
|
|
|
ria = &RelayIdentityAnnouncement{
|
|
Event: ev,
|
|
Content: &content,
|
|
RelayURL: string(relayTag.Value()),
|
|
SigningKey: string(signingKeyTag.Value()),
|
|
EncryptionKey: string(encryptionKeyTag.Value()),
|
|
Version: string(versionTag.Value()),
|
|
NIP11URL: string(nip11URLTag.Value()),
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// Validate performs comprehensive validation of a RelayIdentityAnnouncement.
|
|
func (ria *RelayIdentityAnnouncement) Validate() (err error) {
|
|
if ria == nil {
|
|
return errorf.E("RelayIdentityAnnouncement cannot be nil")
|
|
}
|
|
|
|
if ria.Event == nil {
|
|
return errorf.E("event cannot be nil")
|
|
}
|
|
|
|
// Validate event signature
|
|
if _, err = ria.Event.Verify(); chk.E(err) {
|
|
return errorf.E("invalid event signature: %w", err)
|
|
}
|
|
|
|
// Validate required fields
|
|
if ria.Content.Name == "" {
|
|
return errorf.E("name is required")
|
|
}
|
|
|
|
if ria.RelayURL == "" {
|
|
return errorf.E("relay URL is required")
|
|
}
|
|
|
|
if ria.SigningKey == "" {
|
|
return errorf.E("signing key is required")
|
|
}
|
|
|
|
if ria.EncryptionKey == "" {
|
|
return errorf.E("encryption key is required")
|
|
}
|
|
|
|
if ria.Version == "" {
|
|
return errorf.E("version is required")
|
|
}
|
|
|
|
if ria.NIP11URL == "" {
|
|
return errorf.E("NIP-11 URL is required")
|
|
}
|
|
|
|
// Validate hex-encoded keys (should be 64 characters for 32-byte keys)
|
|
if len(ria.SigningKey) != 64 {
|
|
return errorf.E("signing key must be 64 hex characters")
|
|
}
|
|
|
|
if len(ria.EncryptionKey) != 64 {
|
|
return errorf.E("encryption key must be 64 hex characters")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetRelayURL returns the relay WebSocket URL.
|
|
func (ria *RelayIdentityAnnouncement) GetRelayURL() string {
|
|
return ria.RelayURL
|
|
}
|
|
|
|
// GetSigningKey returns the hex-encoded signing public key.
|
|
func (ria *RelayIdentityAnnouncement) GetSigningKey() string {
|
|
return ria.SigningKey
|
|
}
|
|
|
|
// GetEncryptionKey returns the hex-encoded encryption public key.
|
|
func (ria *RelayIdentityAnnouncement) GetEncryptionKey() string {
|
|
return ria.EncryptionKey
|
|
}
|
|
|
|
// GetVersion returns the protocol version.
|
|
func (ria *RelayIdentityAnnouncement) GetVersion() string {
|
|
return ria.Version
|
|
}
|
|
|
|
// GetNIP11URL returns the NIP-11 information document URL.
|
|
func (ria *RelayIdentityAnnouncement) GetNIP11URL() string {
|
|
return ria.NIP11URL
|
|
}
|
|
|
|
// GetName returns the relay name from the content.
|
|
func (ria *RelayIdentityAnnouncement) GetName() string {
|
|
if ria.Content == nil {
|
|
return ""
|
|
}
|
|
return ria.Content.Name
|
|
}
|
|
|
|
// GetDescription returns the relay description from the content.
|
|
func (ria *RelayIdentityAnnouncement) GetDescription() string {
|
|
if ria.Content == nil {
|
|
return ""
|
|
}
|
|
return ria.Content.Description
|
|
}
|
|
|
|
// GetContact returns the relay contact information from the content.
|
|
func (ria *RelayIdentityAnnouncement) GetContact() string {
|
|
if ria.Content == nil {
|
|
return ""
|
|
}
|
|
return ria.Content.Contact
|
|
}
|