456 lines
11 KiB
Go
456 lines
11 KiB
Go
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 TrustGraph
|
|
func ParseTrustGraph(ev *event.E) (*TrustGraph, 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 &TrustGraph{
|
|
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
|
|
}
|