Files
next.orly.dev/pkg/find/builder.go
2025-11-23 08:15:06 +00:00

389 lines
11 KiB
Go

package find
import (
"fmt"
"strconv"
"time"
"git.mleku.dev/mleku/nostr/encoders/event"
"git.mleku.dev/mleku/nostr/encoders/tag"
"git.mleku.dev/mleku/nostr/encoders/timestamp"
"git.mleku.dev/mleku/nostr/interfaces/signer"
)
// NewRegistrationProposal creates a new registration proposal event (kind 30100)
func NewRegistrationProposal(name, action string, signer signer.I) (*event.E, error) {
// Validate and normalize name
name = NormalizeName(name)
if err := ValidateName(name); err != nil {
return nil, fmt.Errorf("invalid name: %w", err)
}
// Validate action
if action != ActionRegister && action != ActionTransfer {
return nil, fmt.Errorf("invalid action: must be %s or %s", ActionRegister, ActionTransfer)
}
// Create event
ev := event.New()
ev.Kind = KindRegistrationProposal
ev.CreatedAt = timestamp.Now().V
ev.Pubkey = signer.Pub()
// Build tags
tags := tag.NewS()
tags.Append(tag.NewFromAny("d", name))
tags.Append(tag.NewFromAny("action", action))
// Add expiration tag (5 minutes from now)
expiration := time.Now().Add(ProposalExpiry).Unix()
tags.Append(tag.NewFromAny("expiration", strconv.FormatInt(expiration, 10)))
ev.Tags = tags
ev.Content = []byte{}
// Sign the event
if err := ev.Sign(signer); err != nil {
return nil, fmt.Errorf("failed to sign event: %w", err)
}
return ev, nil
}
// NewRegistrationProposalWithTransfer creates a transfer proposal with previous owner signature
func NewRegistrationProposalWithTransfer(name, prevOwner, prevSig string, signer signer.I) (*event.E, error) {
// Create base proposal
ev, err := NewRegistrationProposal(name, ActionTransfer, signer)
if err != nil {
return nil, err
}
// Add transfer-specific tags
ev.Tags.Append(tag.NewFromAny("prev_owner", prevOwner))
ev.Tags.Append(tag.NewFromAny("prev_sig", prevSig))
// Re-sign after adding tags
if err := ev.Sign(signer); err != nil {
return nil, fmt.Errorf("failed to sign transfer event: %w", err)
}
return ev, nil
}
// NewAttestation creates a new attestation event (kind 20100)
func NewAttestation(proposalID, decision string, weight int, reason, serviceURL string, signer signer.I) (*event.E, error) {
// Validate decision
if decision != DecisionApprove && decision != DecisionReject && decision != DecisionAbstain {
return nil, fmt.Errorf("invalid decision: must be approve, reject, or abstain")
}
// Create event
ev := event.New()
ev.Kind = KindAttestation
ev.CreatedAt = timestamp.Now().V
ev.Pubkey = signer.Pub()
// Build tags
tags := tag.NewS()
tags.Append(tag.NewFromAny("e", proposalID))
tags.Append(tag.NewFromAny("decision", decision))
if weight > 0 {
tags.Append(tag.NewFromAny("weight", strconv.Itoa(weight)))
}
if reason != "" {
tags.Append(tag.NewFromAny("reason", reason))
}
if serviceURL != "" {
tags.Append(tag.NewFromAny("service", serviceURL))
}
// Add expiration tag (3 minutes from now)
expiration := time.Now().Add(AttestationExpiry).Unix()
tags.Append(tag.NewFromAny("expiration", strconv.FormatInt(expiration, 10)))
ev.Tags = tags
ev.Content = []byte{}
// Sign the event
if err := ev.Sign(signer); err != nil {
return nil, fmt.Errorf("failed to sign attestation: %w", err)
}
return ev, nil
}
// NewTrustGraphEvent creates a new trust graph event (kind 30101)
func NewTrustGraphEvent(entries []TrustEntry, signer signer.I) (*event.E, error) {
// Validate trust entries
for i, entry := range entries {
if err := ValidateTrustScore(entry.TrustScore); err != nil {
return nil, fmt.Errorf("invalid trust score at index %d: %w", i, err)
}
}
// Create event
ev := event.New()
ev.Kind = KindTrustGraph
ev.CreatedAt = timestamp.Now().V
ev.Pubkey = signer.Pub()
// Build tags
tags := tag.NewS()
tags.Append(tag.NewFromAny("d", "trust-graph"))
// Add trust entries as p tags
for _, entry := range entries {
tags.Append(tag.NewFromAny("p", entry.Pubkey, entry.ServiceURL,
strconv.FormatFloat(entry.TrustScore, 'f', 2, 64)))
}
// Add expiration tag (30 days from now)
expiration := time.Now().Add(TrustGraphExpiry).Unix()
tags.Append(tag.NewFromAny("expiration", strconv.FormatInt(expiration, 10)))
ev.Tags = tags
ev.Content = []byte{}
// Sign the event
if err := ev.Sign(signer); err != nil {
return nil, fmt.Errorf("failed to sign trust graph: %w", err)
}
return ev, nil
}
// NewNameState creates a new name state event (kind 30102)
func NewNameState(name, owner string, registeredAt time.Time, proposalID string,
attestations int, confidence float64, signer signer.I) (*event.E, error) {
// Validate name
name = NormalizeName(name)
if err := ValidateName(name); err != nil {
return nil, fmt.Errorf("invalid name: %w", err)
}
// Create event
ev := event.New()
ev.Kind = KindNameState
ev.CreatedAt = timestamp.Now().V
ev.Pubkey = signer.Pub()
// Build tags
tags := tag.NewS()
tags.Append(tag.NewFromAny("d", name))
tags.Append(tag.NewFromAny("owner", owner))
tags.Append(tag.NewFromAny("registered_at", strconv.FormatInt(registeredAt.Unix(), 10)))
tags.Append(tag.NewFromAny("proposal", proposalID))
tags.Append(tag.NewFromAny("attestations", strconv.Itoa(attestations)))
tags.Append(tag.NewFromAny("confidence", strconv.FormatFloat(confidence, 'f', 2, 64)))
// Add expiration tag (1 year from registration)
expiration := registeredAt.Add(NameRegistrationPeriod).Unix()
tags.Append(tag.NewFromAny("expiration", strconv.FormatInt(expiration, 10)))
ev.Tags = tags
ev.Content = []byte{}
// Sign the event
if err := ev.Sign(signer); err != nil {
return nil, fmt.Errorf("failed to sign name state: %w", err)
}
return ev, nil
}
// NewNameRecord creates a new name record event (kind 30103)
func NewNameRecord(name, recordType, value string, ttl int, signer signer.I) (*event.E, error) {
// Validate name
name = NormalizeName(name)
if err := ValidateName(name); err != nil {
return nil, fmt.Errorf("invalid name: %w", err)
}
// Validate record value
if err := ValidateRecordValue(recordType, value); err != nil {
return nil, err
}
// Create event
ev := event.New()
ev.Kind = KindNameRecords
ev.CreatedAt = timestamp.Now().V
ev.Pubkey = signer.Pub()
// Build tags
tags := tag.NewS()
tags.Append(tag.NewFromAny("d", fmt.Sprintf("%s:%s", name, recordType)))
tags.Append(tag.NewFromAny("name", name))
tags.Append(tag.NewFromAny("type", recordType))
tags.Append(tag.NewFromAny("value", value))
if ttl > 0 {
tags.Append(tag.NewFromAny("ttl", strconv.Itoa(ttl)))
}
ev.Tags = tags
ev.Content = []byte{}
// Sign the event
if err := ev.Sign(signer); err != nil {
return nil, fmt.Errorf("failed to sign name record: %w", err)
}
return ev, nil
}
// NewNameRecordWithPriority creates a name record with priority (for MX, SRV)
func NewNameRecordWithPriority(name, recordType, value string, ttl, priority int, signer signer.I) (*event.E, error) {
// Validate priority
if err := ValidatePriority(priority); err != nil {
return nil, err
}
// Create base record
ev, err := NewNameRecord(name, recordType, value, ttl, signer)
if err != nil {
return nil, err
}
// Add priority tag
ev.Tags.Append(tag.NewFromAny("priority", strconv.Itoa(priority)))
// Re-sign
if err := ev.Sign(signer); err != nil {
return nil, fmt.Errorf("failed to sign record with priority: %w", err)
}
return ev, nil
}
// NewSRVRecord creates an SRV record with all required fields
func NewSRVRecord(name, value string, ttl, priority, weight, port int, signer signer.I) (*event.E, error) {
// Validate SRV-specific fields
if err := ValidatePriority(priority); err != nil {
return nil, err
}
if err := ValidateWeight(weight); err != nil {
return nil, err
}
if err := ValidatePort(port); err != nil {
return nil, err
}
// Create base record
ev, err := NewNameRecord(name, RecordTypeSRV, value, ttl, signer)
if err != nil {
return nil, err
}
// Add SRV-specific tags
ev.Tags.Append(tag.NewFromAny("priority", strconv.Itoa(priority)))
ev.Tags.Append(tag.NewFromAny("weight", strconv.Itoa(weight)))
ev.Tags.Append(tag.NewFromAny("port", strconv.Itoa(port)))
// Re-sign
if err := ev.Sign(signer); err != nil {
return nil, fmt.Errorf("failed to sign SRV record: %w", err)
}
return ev, nil
}
// NewCertificate creates a new certificate event (kind 30104)
func NewCertificate(name, certPubkey string, validFrom, validUntil time.Time,
challenge, challengeProof string, witnesses []WitnessSignature,
algorithm, usage string, signer signer.I) (*event.E, error) {
// Validate name
name = NormalizeName(name)
if err := ValidateName(name); err != nil {
return nil, fmt.Errorf("invalid name: %w", err)
}
// Create event
ev := event.New()
ev.Kind = KindCertificate
ev.CreatedAt = timestamp.Now().V
ev.Pubkey = signer.Pub()
// Build tags
tags := tag.NewS()
tags.Append(tag.NewFromAny("d", name))
tags.Append(tag.NewFromAny("name", name))
tags.Append(tag.NewFromAny("cert_pubkey", certPubkey))
tags.Append(tag.NewFromAny("valid_from", strconv.FormatInt(validFrom.Unix(), 10)))
tags.Append(tag.NewFromAny("valid_until", strconv.FormatInt(validUntil.Unix(), 10)))
tags.Append(tag.NewFromAny("challenge", challenge))
tags.Append(tag.NewFromAny("challenge_proof", challengeProof))
// Add witness signatures
for _, w := range witnesses {
tags.Append(tag.NewFromAny("witness", w.Pubkey, w.Signature))
}
ev.Tags = tags
// Add metadata to content
content := fmt.Sprintf(`{"algorithm":"%s","usage":"%s"}`, algorithm, usage)
ev.Content = []byte(content)
// Sign the event
if err := ev.Sign(signer); err != nil {
return nil, fmt.Errorf("failed to sign certificate: %w", err)
}
return ev, nil
}
// NewWitnessService creates a new witness service info event (kind 30105)
func NewWitnessService(endpoint string, challenges []string, maxValidity, fee int,
reputationID, description, contact string, signer signer.I) (*event.E, error) {
// Create event
ev := event.New()
ev.Kind = KindWitnessService
ev.CreatedAt = timestamp.Now().V
ev.Pubkey = signer.Pub()
// Build tags
tags := tag.NewS()
tags.Append(tag.NewFromAny("d", "witness-service"))
tags.Append(tag.NewFromAny("endpoint", endpoint))
for _, ch := range challenges {
tags.Append(tag.NewFromAny("challenges", ch))
}
if maxValidity > 0 {
tags.Append(tag.NewFromAny("max_validity", strconv.Itoa(maxValidity)))
}
if fee > 0 {
tags.Append(tag.NewFromAny("fee", strconv.Itoa(fee)))
}
if reputationID != "" {
tags.Append(tag.NewFromAny("reputation", reputationID))
}
// Add expiration tag (180 days from now)
expiration := time.Now().Add(WitnessServiceExpiry).Unix()
tags.Append(tag.NewFromAny("expiration", strconv.FormatInt(expiration, 10)))
ev.Tags = tags
// Add metadata to content
content := fmt.Sprintf(`{"description":"%s","contact":"%s"}`, description, contact)
ev.Content = []byte(content)
// Sign the event
if err := ev.Sign(signer); err != nil {
return nil, fmt.Errorf("failed to sign witness service: %w", err)
}
return ev, nil
}