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.
455 lines
11 KiB
455 lines
11 KiB
package find |
|
|
|
import ( |
|
"encoding/json" |
|
"fmt" |
|
"strconv" |
|
"time" |
|
|
|
"next.orly.dev/pkg/encoders/event" |
|
"next.orly.dev/pkg/encoders/tag" |
|
) |
|
|
|
// getTagValue retrieves the value of the first tag with the given key |
|
func getTagValue(ev *event.E, key string) string { |
|
t := ev.Tags.GetFirst([]byte(key)) |
|
if t == nil { |
|
return "" |
|
} |
|
return string(t.Value()) |
|
} |
|
|
|
// getAllTags retrieves all tags with the given key |
|
func getAllTags(ev *event.E, key string) []*tag.T { |
|
return ev.Tags.GetAll([]byte(key)) |
|
} |
|
|
|
// ParseRegistrationProposal parses a kind 30100 event into a RegistrationProposal |
|
func ParseRegistrationProposal(ev *event.E) (*RegistrationProposal, error) { |
|
if uint16(ev.Kind) != KindRegistrationProposal { |
|
return nil, fmt.Errorf("invalid event kind: expected %d, got %d", KindRegistrationProposal, ev.Kind) |
|
} |
|
|
|
name := getTagValue(ev, "d") |
|
if name == "" { |
|
return nil, fmt.Errorf("missing 'd' tag (name)") |
|
} |
|
|
|
action := getTagValue(ev, "action") |
|
if action == "" { |
|
return nil, fmt.Errorf("missing 'action' tag") |
|
} |
|
|
|
expirationStr := getTagValue(ev, "expiration") |
|
var expiration time.Time |
|
if expirationStr != "" { |
|
expirationUnix, err := strconv.ParseInt(expirationStr, 10, 64) |
|
if err != nil { |
|
return nil, fmt.Errorf("invalid expiration timestamp: %w", err) |
|
} |
|
expiration = time.Unix(expirationUnix, 0) |
|
} |
|
|
|
proposal := &RegistrationProposal{ |
|
Event: ev, |
|
Name: name, |
|
Action: action, |
|
PrevOwner: getTagValue(ev, "prev_owner"), |
|
PrevSig: getTagValue(ev, "prev_sig"), |
|
Expiration: expiration, |
|
} |
|
|
|
return proposal, nil |
|
} |
|
|
|
// ParseAttestation parses a kind 20100 event into an Attestation |
|
func ParseAttestation(ev *event.E) (*Attestation, error) { |
|
if uint16(ev.Kind) != KindAttestation { |
|
return nil, fmt.Errorf("invalid event kind: expected %d, got %d", KindAttestation, ev.Kind) |
|
} |
|
|
|
proposalID := getTagValue(ev, "e") |
|
if proposalID == "" { |
|
return nil, fmt.Errorf("missing 'e' tag (proposal ID)") |
|
} |
|
|
|
decision := getTagValue(ev, "decision") |
|
if decision == "" { |
|
return nil, fmt.Errorf("missing 'decision' tag") |
|
} |
|
|
|
weightStr := getTagValue(ev, "weight") |
|
weight := 100 // default weight |
|
if weightStr != "" { |
|
w, err := strconv.Atoi(weightStr) |
|
if err != nil { |
|
return nil, fmt.Errorf("invalid weight value: %w", err) |
|
} |
|
weight = w |
|
} |
|
|
|
expirationStr := getTagValue(ev, "expiration") |
|
var expiration time.Time |
|
if expirationStr != "" { |
|
expirationUnix, err := strconv.ParseInt(expirationStr, 10, 64) |
|
if err != nil { |
|
return nil, fmt.Errorf("invalid expiration timestamp: %w", err) |
|
} |
|
expiration = time.Unix(expirationUnix, 0) |
|
} |
|
|
|
attestation := &Attestation{ |
|
Event: ev, |
|
ProposalID: proposalID, |
|
Decision: decision, |
|
Weight: weight, |
|
Reason: getTagValue(ev, "reason"), |
|
ServiceURL: getTagValue(ev, "service"), |
|
Expiration: expiration, |
|
} |
|
|
|
return attestation, nil |
|
} |
|
|
|
// ParseTrustGraph parses a kind 30101 event into a TrustGraphEvent |
|
func ParseTrustGraph(ev *event.E) (*TrustGraphEvent, error) { |
|
if uint16(ev.Kind) != KindTrustGraph { |
|
return nil, fmt.Errorf("invalid event kind: expected %d, got %d", KindTrustGraph, ev.Kind) |
|
} |
|
|
|
expirationStr := getTagValue(ev, "expiration") |
|
var expiration time.Time |
|
if expirationStr != "" { |
|
expirationUnix, err := strconv.ParseInt(expirationStr, 10, 64) |
|
if err != nil { |
|
return nil, fmt.Errorf("invalid expiration timestamp: %w", err) |
|
} |
|
expiration = time.Unix(expirationUnix, 0) |
|
} |
|
|
|
// Parse p tags (trust entries) |
|
var entries []TrustEntry |
|
pTags := getAllTags(ev, "p") |
|
for _, t := range pTags { |
|
if len(t.T) < 2 { |
|
continue // Skip malformed tags |
|
} |
|
|
|
pubkey := string(t.T[1]) |
|
serviceURL := "" |
|
trustScore := 0.5 // default |
|
|
|
if len(t.T) > 2 { |
|
serviceURL = string(t.T[2]) |
|
} |
|
|
|
if len(t.T) > 3 { |
|
score, err := strconv.ParseFloat(string(t.T[3]), 64) |
|
if err == nil { |
|
trustScore = score |
|
} |
|
} |
|
|
|
entries = append(entries, TrustEntry{ |
|
Pubkey: pubkey, |
|
ServiceURL: serviceURL, |
|
TrustScore: trustScore, |
|
}) |
|
} |
|
|
|
return &TrustGraphEvent{ |
|
Event: ev, |
|
Entries: entries, |
|
Expiration: expiration, |
|
}, nil |
|
} |
|
|
|
// ParseNameState parses a kind 30102 event into a NameState |
|
func ParseNameState(ev *event.E) (*NameState, error) { |
|
if uint16(ev.Kind) != KindNameState { |
|
return nil, fmt.Errorf("invalid event kind: expected %d, got %d", KindNameState, ev.Kind) |
|
} |
|
|
|
name := getTagValue(ev, "d") |
|
if name == "" { |
|
return nil, fmt.Errorf("missing 'd' tag (name)") |
|
} |
|
|
|
owner := getTagValue(ev, "owner") |
|
if owner == "" { |
|
return nil, fmt.Errorf("missing 'owner' tag") |
|
} |
|
|
|
registeredAtStr := getTagValue(ev, "registered_at") |
|
if registeredAtStr == "" { |
|
return nil, fmt.Errorf("missing 'registered_at' tag") |
|
} |
|
registeredAtUnix, err := strconv.ParseInt(registeredAtStr, 10, 64) |
|
if err != nil { |
|
return nil, fmt.Errorf("invalid registered_at timestamp: %w", err) |
|
} |
|
registeredAt := time.Unix(registeredAtUnix, 0) |
|
|
|
attestationsStr := getTagValue(ev, "attestations") |
|
attestations := 0 |
|
if attestationsStr != "" { |
|
a, err := strconv.Atoi(attestationsStr) |
|
if err == nil { |
|
attestations = a |
|
} |
|
} |
|
|
|
confidenceStr := getTagValue(ev, "confidence") |
|
confidence := 0.0 |
|
if confidenceStr != "" { |
|
c, err := strconv.ParseFloat(confidenceStr, 64) |
|
if err == nil { |
|
confidence = c |
|
} |
|
} |
|
|
|
expirationStr := getTagValue(ev, "expiration") |
|
var expiration time.Time |
|
if expirationStr != "" { |
|
expirationUnix, err := strconv.ParseInt(expirationStr, 10, 64) |
|
if err != nil { |
|
return nil, fmt.Errorf("invalid expiration timestamp: %w", err) |
|
} |
|
expiration = time.Unix(expirationUnix, 0) |
|
} |
|
|
|
return &NameState{ |
|
Event: ev, |
|
Name: name, |
|
Owner: owner, |
|
RegisteredAt: registeredAt, |
|
ProposalID: getTagValue(ev, "proposal"), |
|
Attestations: attestations, |
|
Confidence: confidence, |
|
Expiration: expiration, |
|
}, nil |
|
} |
|
|
|
// ParseNameRecord parses a kind 30103 event into a NameRecord |
|
func ParseNameRecord(ev *event.E) (*NameRecord, error) { |
|
if uint16(ev.Kind) != KindNameRecords { |
|
return nil, fmt.Errorf("invalid event kind: expected %d, got %d", KindNameRecords, ev.Kind) |
|
} |
|
|
|
name := getTagValue(ev, "name") |
|
if name == "" { |
|
return nil, fmt.Errorf("missing 'name' tag") |
|
} |
|
|
|
recordType := getTagValue(ev, "type") |
|
if recordType == "" { |
|
return nil, fmt.Errorf("missing 'type' tag") |
|
} |
|
|
|
value := getTagValue(ev, "value") |
|
if value == "" { |
|
return nil, fmt.Errorf("missing 'value' tag") |
|
} |
|
|
|
ttlStr := getTagValue(ev, "ttl") |
|
ttl := 3600 // default TTL |
|
if ttlStr != "" { |
|
t, err := strconv.Atoi(ttlStr) |
|
if err == nil { |
|
ttl = t |
|
} |
|
} |
|
|
|
priorityStr := getTagValue(ev, "priority") |
|
priority := 0 |
|
if priorityStr != "" { |
|
p, err := strconv.Atoi(priorityStr) |
|
if err == nil { |
|
priority = p |
|
} |
|
} |
|
|
|
weightStr := getTagValue(ev, "weight") |
|
weight := 0 |
|
if weightStr != "" { |
|
w, err := strconv.Atoi(weightStr) |
|
if err == nil { |
|
weight = w |
|
} |
|
} |
|
|
|
portStr := getTagValue(ev, "port") |
|
port := 0 |
|
if portStr != "" { |
|
p, err := strconv.Atoi(portStr) |
|
if err == nil { |
|
port = p |
|
} |
|
} |
|
|
|
return &NameRecord{ |
|
Event: ev, |
|
Name: name, |
|
Type: recordType, |
|
Value: value, |
|
TTL: ttl, |
|
Priority: priority, |
|
Weight: weight, |
|
Port: port, |
|
}, nil |
|
} |
|
|
|
// ParseCertificate parses a kind 30104 event into a Certificate |
|
func ParseCertificate(ev *event.E) (*Certificate, error) { |
|
if uint16(ev.Kind) != KindCertificate { |
|
return nil, fmt.Errorf("invalid event kind: expected %d, got %d", KindCertificate, ev.Kind) |
|
} |
|
|
|
name := getTagValue(ev, "name") |
|
if name == "" { |
|
return nil, fmt.Errorf("missing 'name' tag") |
|
} |
|
|
|
certPubkey := getTagValue(ev, "cert_pubkey") |
|
if certPubkey == "" { |
|
return nil, fmt.Errorf("missing 'cert_pubkey' tag") |
|
} |
|
|
|
validFromStr := getTagValue(ev, "valid_from") |
|
if validFromStr == "" { |
|
return nil, fmt.Errorf("missing 'valid_from' tag") |
|
} |
|
validFromUnix, err := strconv.ParseInt(validFromStr, 10, 64) |
|
if err != nil { |
|
return nil, fmt.Errorf("invalid valid_from timestamp: %w", err) |
|
} |
|
validFrom := time.Unix(validFromUnix, 0) |
|
|
|
validUntilStr := getTagValue(ev, "valid_until") |
|
if validUntilStr == "" { |
|
return nil, fmt.Errorf("missing 'valid_until' tag") |
|
} |
|
validUntilUnix, err := strconv.ParseInt(validUntilStr, 10, 64) |
|
if err != nil { |
|
return nil, fmt.Errorf("invalid valid_until timestamp: %w", err) |
|
} |
|
validUntil := time.Unix(validUntilUnix, 0) |
|
|
|
// Parse witness tags |
|
var witnesses []WitnessSignature |
|
witnessTags := getAllTags(ev, "witness") |
|
for _, t := range witnessTags { |
|
if len(t.T) < 3 { |
|
continue // Skip malformed tags |
|
} |
|
|
|
witnesses = append(witnesses, WitnessSignature{ |
|
Pubkey: string(t.T[1]), |
|
Signature: string(t.T[2]), |
|
}) |
|
} |
|
|
|
// Parse content JSON |
|
algorithm := "secp256k1-schnorr" |
|
usage := "tls-replacement" |
|
if len(ev.Content) > 0 { |
|
var metadata map[string]interface{} |
|
if err := json.Unmarshal(ev.Content, &metadata); err == nil { |
|
if alg, ok := metadata["algorithm"].(string); ok { |
|
algorithm = alg |
|
} |
|
if u, ok := metadata["usage"].(string); ok { |
|
usage = u |
|
} |
|
} |
|
} |
|
|
|
return &Certificate{ |
|
Event: ev, |
|
Name: name, |
|
CertPubkey: certPubkey, |
|
ValidFrom: validFrom, |
|
ValidUntil: validUntil, |
|
Challenge: getTagValue(ev, "challenge"), |
|
ChallengeProof: getTagValue(ev, "challenge_proof"), |
|
Witnesses: witnesses, |
|
Algorithm: algorithm, |
|
Usage: usage, |
|
}, nil |
|
} |
|
|
|
// ParseWitnessService parses a kind 30105 event into a WitnessService |
|
func ParseWitnessService(ev *event.E) (*WitnessService, error) { |
|
if uint16(ev.Kind) != KindWitnessService { |
|
return nil, fmt.Errorf("invalid event kind: expected %d, got %d", KindWitnessService, ev.Kind) |
|
} |
|
|
|
endpoint := getTagValue(ev, "endpoint") |
|
if endpoint == "" { |
|
return nil, fmt.Errorf("missing 'endpoint' tag") |
|
} |
|
|
|
// Parse challenge tags |
|
var challenges []string |
|
challengeTags := getAllTags(ev, "challenges") |
|
for _, t := range challengeTags { |
|
if len(t.T) >= 2 { |
|
challenges = append(challenges, string(t.T[1])) |
|
} |
|
} |
|
|
|
maxValidityStr := getTagValue(ev, "max_validity") |
|
maxValidity := 0 |
|
if maxValidityStr != "" { |
|
mv, err := strconv.Atoi(maxValidityStr) |
|
if err == nil { |
|
maxValidity = mv |
|
} |
|
} |
|
|
|
feeStr := getTagValue(ev, "fee") |
|
fee := 0 |
|
if feeStr != "" { |
|
f, err := strconv.Atoi(feeStr) |
|
if err == nil { |
|
fee = f |
|
} |
|
} |
|
|
|
expirationStr := getTagValue(ev, "expiration") |
|
var expiration time.Time |
|
if expirationStr != "" { |
|
expirationUnix, err := strconv.ParseInt(expirationStr, 10, 64) |
|
if err != nil { |
|
return nil, fmt.Errorf("invalid expiration timestamp: %w", err) |
|
} |
|
expiration = time.Unix(expirationUnix, 0) |
|
} |
|
|
|
// Parse content JSON |
|
description := "" |
|
contact := "" |
|
if len(ev.Content) > 0 { |
|
var metadata map[string]interface{} |
|
if err := json.Unmarshal(ev.Content, &metadata); err == nil { |
|
if desc, ok := metadata["description"].(string); ok { |
|
description = desc |
|
} |
|
if cont, ok := metadata["contact"].(string); ok { |
|
contact = cont |
|
} |
|
} |
|
} |
|
|
|
return &WitnessService{ |
|
Event: ev, |
|
Endpoint: endpoint, |
|
Challenges: challenges, |
|
MaxValidity: maxValidity, |
|
Fee: fee, |
|
ReputationID: getTagValue(ev, "reputation"), |
|
Description: description, |
|
Contact: contact, |
|
Expiration: expiration, |
|
}, nil |
|
}
|
|
|