You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
388 lines
11 KiB
388 lines
11 KiB
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 |
|
}
|
|
|