clone

a self-hosted multitenant nostr relay with sites, file storage, git hosting, and inbox and outbox delivery

feat/ui-updates
tinyrelay / internal / records / sign.go
sign.go 2.5K
1package records23import (4 "context"5 "crypto/sha256"6 "encoding/hex"7 "fmt"8 "github.com/FelineStateMachine/tinyrelay/internal/event"9 "net/url"10 "strings"11 "time"12)1314// GenerateEphemeral signs a relay-owned response without invoking the15// durable records callback. It is used for protocol responses such as the16// NIP-43 invite request, which must be relay-signed but never stored.17func (s *Service) GenerateEphemeral(ctx context.Context, kind int, tags [][]string, content string, now int64) (event.Event, error) {18 if err := ctx.Err(); err != nil {19 return event.Event{}, err20 }21 if now == 0 {22 now = time.Now().Unix()23 }24 // Protocol proofs use wall-clock time. The immutable relay key is safe to25 // read concurrently; taking the projection lock here would invert the26 // router/records lock order for responses generated inside a REQ.27 out := event.Event{CreatedAt: now, Kind: kind, Tags: tags, Content: content}28 if err := event.Sign(&out, s.secret); err != nil {29 return event.Event{}, err30 }31 return out, nil32}3334// SignNIP98 creates a short-lived HTTP authorization proof with the durable35// relay identity. The caller supplies the exact URL Git will request and the36// body hash when the method has a payload.37func (s *Service) SignNIP98(ctx context.Context, method, rawURL, payloadHash string, now int64) (event.Event, error) {38 if _, err := url.ParseRequestURI(rawURL); err != nil || !strings.Contains(rawURL, "://") {39 return event.Event{}, fmt.Errorf("records: invalid NIP-98 URL: %w", err)40 }41 secret, err := event.GenerateKey()42 if err != nil {43 return event.Event{}, err44 }45 tags := [][]string{{"u", rawURL}, {"method", strings.ToUpper(method)}, {"nonce", secret}}46 if payloadHash != "" {47 if len(payloadHash) != sha256.Size*2 {48 return event.Event{}, fmt.Errorf("records: invalid NIP-98 payload hash")49 }50 if _, err := hex.DecodeString(payloadHash); err != nil {51 return event.Event{}, fmt.Errorf("records: invalid NIP-98 payload hash: %w", err)52 }53 tags = append(tags, []string{"payload", payloadHash})54 }55 if now == 0 {56 now = time.Now().Unix()57 }58 return s.GenerateEphemeral(ctx, 27235, tags, "", now)59}6061// SignNIP42 answers a relay challenge without persisting an AUTH event.62func (s *Service) SignNIP42(ctx context.Context, relayURL, challenge string, now int64) (event.Event, error) {63 if relayURL == "" || challenge == "" {64 return event.Event{}, fmt.Errorf("records: missing NIP-42 challenge")65 }66 return s.GenerateEphemeral(ctx, event.KIND_AUTH, [][]string{{"relay", relayURL}, {"challenge", challenge}}, "", now)67}