builder.go raw

   1  package find
   2  
   3  import (
   4  	"fmt"
   5  	"strconv"
   6  	"time"
   7  
   8  	"next.orly.dev/pkg/nostr/encoders/event"
   9  	"next.orly.dev/pkg/nostr/encoders/tag"
  10  	"next.orly.dev/pkg/nostr/encoders/timestamp"
  11  	"next.orly.dev/pkg/nostr/interfaces/signer"
  12  )
  13  
  14  // NewRegistrationProposal creates a new registration proposal event (kind 30100)
  15  func NewRegistrationProposal(name, action string, signer signer.I) (*event.E, error) {
  16  	// Validate and normalize name
  17  	name = NormalizeName(name)
  18  	if err := ValidateName(name); err != nil {
  19  		return nil, fmt.Errorf("invalid name: %w", err)
  20  	}
  21  
  22  	// Validate action
  23  	if action != ActionRegister && action != ActionTransfer {
  24  		return nil, fmt.Errorf("invalid action: must be %s or %s", ActionRegister, ActionTransfer)
  25  	}
  26  
  27  	// Create event
  28  	ev := event.New()
  29  	ev.Kind = KindRegistrationProposal
  30  	ev.CreatedAt = timestamp.Now().V
  31  	ev.Pubkey = signer.Pub()
  32  
  33  	// Build tags
  34  	tags := tag.NewS()
  35  	tags.Append(tag.NewFromAny("d", name))
  36  	tags.Append(tag.NewFromAny("action", action))
  37  
  38  	// Add expiration tag (5 minutes from now)
  39  	expiration := time.Now().Add(ProposalExpiry).Unix()
  40  	tags.Append(tag.NewFromAny("expiration", strconv.FormatInt(expiration, 10)))
  41  
  42  	ev.Tags = tags
  43  	ev.Content = []byte{}
  44  
  45  	// Sign the event
  46  	if err := ev.Sign(signer); err != nil {
  47  		return nil, fmt.Errorf("failed to sign event: %w", err)
  48  	}
  49  
  50  	return ev, nil
  51  }
  52  
  53  // NewRegistrationProposalWithTransfer creates a transfer proposal with previous owner signature
  54  func NewRegistrationProposalWithTransfer(name, prevOwner, prevSig string, signer signer.I) (*event.E, error) {
  55  	// Create base proposal
  56  	ev, err := NewRegistrationProposal(name, ActionTransfer, signer)
  57  	if err != nil {
  58  		return nil, err
  59  	}
  60  
  61  	// Add transfer-specific tags
  62  	ev.Tags.Append(tag.NewFromAny("prev_owner", prevOwner))
  63  	ev.Tags.Append(tag.NewFromAny("prev_sig", prevSig))
  64  
  65  	// Re-sign after adding tags
  66  	if err := ev.Sign(signer); err != nil {
  67  		return nil, fmt.Errorf("failed to sign transfer event: %w", err)
  68  	}
  69  
  70  	return ev, nil
  71  }
  72  
  73  // NewAttestation creates a new attestation event (kind 20100)
  74  func NewAttestation(proposalID, decision string, weight int, reason, serviceURL string, signer signer.I) (*event.E, error) {
  75  	// Validate decision
  76  	if decision != DecisionApprove && decision != DecisionReject && decision != DecisionAbstain {
  77  		return nil, fmt.Errorf("invalid decision: must be approve, reject, or abstain")
  78  	}
  79  
  80  	// Create event
  81  	ev := event.New()
  82  	ev.Kind = KindAttestation
  83  	ev.CreatedAt = timestamp.Now().V
  84  	ev.Pubkey = signer.Pub()
  85  
  86  	// Build tags
  87  	tags := tag.NewS()
  88  	tags.Append(tag.NewFromAny("e", proposalID))
  89  	tags.Append(tag.NewFromAny("decision", decision))
  90  
  91  	if weight > 0 {
  92  		tags.Append(tag.NewFromAny("weight", strconv.Itoa(weight)))
  93  	}
  94  
  95  	if reason != "" {
  96  		tags.Append(tag.NewFromAny("reason", reason))
  97  	}
  98  
  99  	if serviceURL != "" {
 100  		tags.Append(tag.NewFromAny("service", serviceURL))
 101  	}
 102  
 103  	// Add expiration tag (3 minutes from now)
 104  	expiration := time.Now().Add(AttestationExpiry).Unix()
 105  	tags.Append(tag.NewFromAny("expiration", strconv.FormatInt(expiration, 10)))
 106  
 107  	ev.Tags = tags
 108  	ev.Content = []byte{}
 109  
 110  	// Sign the event
 111  	if err := ev.Sign(signer); err != nil {
 112  		return nil, fmt.Errorf("failed to sign attestation: %w", err)
 113  	}
 114  
 115  	return ev, nil
 116  }
 117  
 118  // NewTrustGraphEvent creates a new trust graph event (kind 30101)
 119  func NewTrustGraphEvent(entries []TrustEntry, signer signer.I) (*event.E, error) {
 120  	// Validate trust entries
 121  	for i, entry := range entries {
 122  		if err := ValidateTrustScore(entry.TrustScore); err != nil {
 123  			return nil, fmt.Errorf("invalid trust score at index %d: %w", i, err)
 124  		}
 125  	}
 126  
 127  	// Create event
 128  	ev := event.New()
 129  	ev.Kind = KindTrustGraph
 130  	ev.CreatedAt = timestamp.Now().V
 131  	ev.Pubkey = signer.Pub()
 132  
 133  	// Build tags
 134  	tags := tag.NewS()
 135  	tags.Append(tag.NewFromAny("d", "trust-graph"))
 136  
 137  	// Add trust entries as p tags
 138  	for _, entry := range entries {
 139  		tags.Append(tag.NewFromAny("p", entry.Pubkey, entry.ServiceURL,
 140  			strconv.FormatFloat(entry.TrustScore, 'f', 2, 64)))
 141  	}
 142  
 143  	// Add expiration tag (30 days from now)
 144  	expiration := time.Now().Add(TrustGraphExpiry).Unix()
 145  	tags.Append(tag.NewFromAny("expiration", strconv.FormatInt(expiration, 10)))
 146  
 147  	ev.Tags = tags
 148  	ev.Content = []byte{}
 149  
 150  	// Sign the event
 151  	if err := ev.Sign(signer); err != nil {
 152  		return nil, fmt.Errorf("failed to sign trust graph: %w", err)
 153  	}
 154  
 155  	return ev, nil
 156  }
 157  
 158  // NewNameState creates a new name state event (kind 30102)
 159  func NewNameState(name, owner string, registeredAt time.Time, proposalID string,
 160  	attestations int, confidence float64, signer signer.I) (*event.E, error) {
 161  
 162  	// Validate name
 163  	name = NormalizeName(name)
 164  	if err := ValidateName(name); err != nil {
 165  		return nil, fmt.Errorf("invalid name: %w", err)
 166  	}
 167  
 168  	// Create event
 169  	ev := event.New()
 170  	ev.Kind = KindNameState
 171  	ev.CreatedAt = timestamp.Now().V
 172  	ev.Pubkey = signer.Pub()
 173  
 174  	// Build tags
 175  	tags := tag.NewS()
 176  	tags.Append(tag.NewFromAny("d", name))
 177  	tags.Append(tag.NewFromAny("owner", owner))
 178  	tags.Append(tag.NewFromAny("registered_at", strconv.FormatInt(registeredAt.Unix(), 10)))
 179  	tags.Append(tag.NewFromAny("proposal", proposalID))
 180  	tags.Append(tag.NewFromAny("attestations", strconv.Itoa(attestations)))
 181  	tags.Append(tag.NewFromAny("confidence", strconv.FormatFloat(confidence, 'f', 2, 64)))
 182  
 183  	// Add expiration tag (1 year from registration)
 184  	expiration := registeredAt.Add(NameRegistrationPeriod).Unix()
 185  	tags.Append(tag.NewFromAny("expiration", strconv.FormatInt(expiration, 10)))
 186  
 187  	ev.Tags = tags
 188  	ev.Content = []byte{}
 189  
 190  	// Sign the event
 191  	if err := ev.Sign(signer); err != nil {
 192  		return nil, fmt.Errorf("failed to sign name state: %w", err)
 193  	}
 194  
 195  	return ev, nil
 196  }
 197  
 198  // NewNameRecord creates a new name record event (kind 30103)
 199  func NewNameRecord(name, recordType, value string, ttl int, signer signer.I) (*event.E, error) {
 200  	// Validate name
 201  	name = NormalizeName(name)
 202  	if err := ValidateName(name); err != nil {
 203  		return nil, fmt.Errorf("invalid name: %w", err)
 204  	}
 205  
 206  	// Validate record value
 207  	if err := ValidateRecordValue(recordType, value); err != nil {
 208  		return nil, err
 209  	}
 210  
 211  	// Create event
 212  	ev := event.New()
 213  	ev.Kind = KindNameRecords
 214  	ev.CreatedAt = timestamp.Now().V
 215  	ev.Pubkey = signer.Pub()
 216  
 217  	// Build tags
 218  	tags := tag.NewS()
 219  	tags.Append(tag.NewFromAny("d", fmt.Sprintf("%s:%s", name, recordType)))
 220  	tags.Append(tag.NewFromAny("name", name))
 221  	tags.Append(tag.NewFromAny("type", recordType))
 222  	tags.Append(tag.NewFromAny("value", value))
 223  
 224  	if ttl > 0 {
 225  		tags.Append(tag.NewFromAny("ttl", strconv.Itoa(ttl)))
 226  	}
 227  
 228  	ev.Tags = tags
 229  	ev.Content = []byte{}
 230  
 231  	// Sign the event
 232  	if err := ev.Sign(signer); err != nil {
 233  		return nil, fmt.Errorf("failed to sign name record: %w", err)
 234  	}
 235  
 236  	return ev, nil
 237  }
 238  
 239  // NewNameRecordWithPriority creates a name record with priority (for MX, SRV)
 240  func NewNameRecordWithPriority(name, recordType, value string, ttl, priority int, signer signer.I) (*event.E, error) {
 241  	// Validate priority
 242  	if err := ValidatePriority(priority); err != nil {
 243  		return nil, err
 244  	}
 245  
 246  	// Create base record
 247  	ev, err := NewNameRecord(name, recordType, value, ttl, signer)
 248  	if err != nil {
 249  		return nil, err
 250  	}
 251  
 252  	// Add priority tag
 253  	ev.Tags.Append(tag.NewFromAny("priority", strconv.Itoa(priority)))
 254  
 255  	// Re-sign
 256  	if err := ev.Sign(signer); err != nil {
 257  		return nil, fmt.Errorf("failed to sign record with priority: %w", err)
 258  	}
 259  
 260  	return ev, nil
 261  }
 262  
 263  // NewSRVRecord creates an SRV record with all required fields
 264  func NewSRVRecord(name, value string, ttl, priority, weight, port int, signer signer.I) (*event.E, error) {
 265  	// Validate SRV-specific fields
 266  	if err := ValidatePriority(priority); err != nil {
 267  		return nil, err
 268  	}
 269  	if err := ValidateWeight(weight); err != nil {
 270  		return nil, err
 271  	}
 272  	if err := ValidatePort(port); err != nil {
 273  		return nil, err
 274  	}
 275  
 276  	// Create base record
 277  	ev, err := NewNameRecord(name, RecordTypeSRV, value, ttl, signer)
 278  	if err != nil {
 279  		return nil, err
 280  	}
 281  
 282  	// Add SRV-specific tags
 283  	ev.Tags.Append(tag.NewFromAny("priority", strconv.Itoa(priority)))
 284  	ev.Tags.Append(tag.NewFromAny("weight", strconv.Itoa(weight)))
 285  	ev.Tags.Append(tag.NewFromAny("port", strconv.Itoa(port)))
 286  
 287  	// Re-sign
 288  	if err := ev.Sign(signer); err != nil {
 289  		return nil, fmt.Errorf("failed to sign SRV record: %w", err)
 290  	}
 291  
 292  	return ev, nil
 293  }
 294  
 295  // NewCertificate creates a new certificate event (kind 30104)
 296  func NewCertificate(name, certPubkey string, validFrom, validUntil time.Time,
 297  	challenge, challengeProof string, witnesses []WitnessSignature,
 298  	algorithm, usage string, signer signer.I) (*event.E, error) {
 299  
 300  	// Validate name
 301  	name = NormalizeName(name)
 302  	if err := ValidateName(name); err != nil {
 303  		return nil, fmt.Errorf("invalid name: %w", err)
 304  	}
 305  
 306  	// Create event
 307  	ev := event.New()
 308  	ev.Kind = KindCertificate
 309  	ev.CreatedAt = timestamp.Now().V
 310  	ev.Pubkey = signer.Pub()
 311  
 312  	// Build tags
 313  	tags := tag.NewS()
 314  	tags.Append(tag.NewFromAny("d", name))
 315  	tags.Append(tag.NewFromAny("name", name))
 316  	tags.Append(tag.NewFromAny("cert_pubkey", certPubkey))
 317  	tags.Append(tag.NewFromAny("valid_from", strconv.FormatInt(validFrom.Unix(), 10)))
 318  	tags.Append(tag.NewFromAny("valid_until", strconv.FormatInt(validUntil.Unix(), 10)))
 319  	tags.Append(tag.NewFromAny("challenge", challenge))
 320  	tags.Append(tag.NewFromAny("challenge_proof", challengeProof))
 321  
 322  	// Add witness signatures
 323  	for _, w := range witnesses {
 324  		tags.Append(tag.NewFromAny("witness", w.Pubkey, w.Signature))
 325  	}
 326  
 327  	ev.Tags = tags
 328  
 329  	// Add metadata to content
 330  	content := fmt.Sprintf(`{"algorithm":"%s","usage":"%s"}`, algorithm, usage)
 331  	ev.Content = []byte(content)
 332  
 333  	// Sign the event
 334  	if err := ev.Sign(signer); err != nil {
 335  		return nil, fmt.Errorf("failed to sign certificate: %w", err)
 336  	}
 337  
 338  	return ev, nil
 339  }
 340  
 341  // NewWitnessService creates a new witness service info event (kind 30105)
 342  func NewWitnessService(endpoint string, challenges []string, maxValidity, fee int,
 343  	reputationID, description, contact string, signer signer.I) (*event.E, error) {
 344  
 345  	// Create event
 346  	ev := event.New()
 347  	ev.Kind = KindWitnessService
 348  	ev.CreatedAt = timestamp.Now().V
 349  	ev.Pubkey = signer.Pub()
 350  
 351  	// Build tags
 352  	tags := tag.NewS()
 353  	tags.Append(tag.NewFromAny("d", "witness-service"))
 354  	tags.Append(tag.NewFromAny("endpoint", endpoint))
 355  
 356  	for _, ch := range challenges {
 357  		tags.Append(tag.NewFromAny("challenges", ch))
 358  	}
 359  
 360  	if maxValidity > 0 {
 361  		tags.Append(tag.NewFromAny("max_validity", strconv.Itoa(maxValidity)))
 362  	}
 363  
 364  	if fee > 0 {
 365  		tags.Append(tag.NewFromAny("fee", strconv.Itoa(fee)))
 366  	}
 367  
 368  	if reputationID != "" {
 369  		tags.Append(tag.NewFromAny("reputation", reputationID))
 370  	}
 371  
 372  	// Add expiration tag (180 days from now)
 373  	expiration := time.Now().Add(WitnessServiceExpiry).Unix()
 374  	tags.Append(tag.NewFromAny("expiration", strconv.FormatInt(expiration, 10)))
 375  
 376  	ev.Tags = tags
 377  
 378  	// Add metadata to content
 379  	content := fmt.Sprintf(`{"description":"%s","contact":"%s"}`, description, contact)
 380  	ev.Content = []byte(content)
 381  
 382  	// Sign the event
 383  	if err := ev.Sign(signer); err != nil {
 384  		return nil, fmt.Errorf("failed to sign witness service: %w", err)
 385  	}
 386  
 387  	return ev, nil
 388  }
 389