// Package addressbook manages contacts and their public keys package addressbook import ( "crypto/ed25519" "encoding/hex" "encoding/json" "fmt" "os" "sync" "time" ) // Contact represents a contact in the address book type Contact struct { Name string `json:"name"` PublicKey string `json:"public_key"` // hex-encoded Ed25519 public key OnionAddress string `json:"onion_address"` // Tor hidden service address Fingerprint string `json:"fingerprint"` // Short fingerprint for verification AddedAt time.Time `json:"added_at"` LastSeen time.Time `json:"last_seen"` Verified bool `json:"verified"` // Manual verification flag Notes string `json:"notes"` Metadata map[string]string `json:"metadata,omitempty"` } // AddressBook manages a collection of contacts type AddressBook struct { contacts map[string]*Contact // key = public key hex mu sync.RWMutex path string } // NewAddressBook creates a new empty address book func NewAddressBook() *AddressBook { return &AddressBook{ contacts: make(map[string]*Contact), } } // LoadAddressBook loads an address book from disk func LoadAddressBook(path string) (*AddressBook, error) { data, err := os.ReadFile(path) if err != nil { return nil, fmt.Errorf("failed to read address book: %w", err) } var contacts []*Contact if err := json.Unmarshal(data, &contacts); err != nil { return nil, fmt.Errorf("failed to parse address book: %w", err) } ab := &AddressBook{ contacts: make(map[string]*Contact), path: path, } for _, contact := range contacts { ab.contacts[contact.PublicKey] = contact } return ab, nil } // Save saves the address book to disk func (ab *AddressBook) Save(path string) error { ab.mu.RLock() defer ab.mu.RUnlock() // Convert map to slice contacts := make([]*Contact, 0, len(ab.contacts)) for _, contact := range ab.contacts { contacts = append(contacts, contact) } // Marshal to JSON data, err := json.MarshalIndent(contacts, "", " ") if err != nil { return fmt.Errorf("failed to marshal address book: %w", err) } // Write to file if err := os.WriteFile(path, data, 0600); err != nil { return fmt.Errorf("failed to write address book: %w", err) } ab.path = path return nil } // AddContact adds a new contact to the address book func (ab *AddressBook) AddContact(contact *Contact) error { ab.mu.Lock() defer ab.mu.Unlock() // Validate public key pubKeyBytes, err := hex.DecodeString(contact.PublicKey) if err != nil { return fmt.Errorf("invalid public key hex: %w", err) } if len(pubKeyBytes) != ed25519.PublicKeySize { return fmt.Errorf("invalid public key size: expected %d, got %d", ed25519.PublicKeySize, len(pubKeyBytes)) } // Generate fingerprint (first 8 bytes of public key) contact.Fingerprint = hex.EncodeToString(pubKeyBytes[:8]) contact.AddedAt = time.Now() contact.LastSeen = time.Now() ab.contacts[contact.PublicKey] = contact return nil } // GetContact retrieves a contact by public key func (ab *AddressBook) GetContact(publicKey string) (*Contact, bool) { ab.mu.RLock() defer ab.mu.RUnlock() contact, exists := ab.contacts[publicKey] return contact, exists } // GetContactByName retrieves a contact by name (first match) func (ab *AddressBook) GetContactByName(name string) (*Contact, bool) { ab.mu.RLock() defer ab.mu.RUnlock() for _, contact := range ab.contacts { if contact.Name == name { return contact, true } } return nil, false } // RemoveContact removes a contact from the address book func (ab *AddressBook) RemoveContact(publicKey string) bool { ab.mu.Lock() defer ab.mu.Unlock() if _, exists := ab.contacts[publicKey]; exists { delete(ab.contacts, publicKey) return true } return false } // UpdateLastSeen updates the last seen timestamp for a contact func (ab *AddressBook) UpdateLastSeen(publicKey string) { ab.mu.Lock() defer ab.mu.Unlock() if contact, exists := ab.contacts[publicKey]; exists { contact.LastSeen = time.Now() } } // MarkVerified marks a contact as manually verified func (ab *AddressBook) MarkVerified(publicKey string, verified bool) error { ab.mu.Lock() defer ab.mu.Unlock() contact, exists := ab.contacts[publicKey] if !exists { return fmt.Errorf("contact not found") } contact.Verified = verified return nil } // ListContacts returns all contacts func (ab *AddressBook) ListContacts() []*Contact { ab.mu.RLock() defer ab.mu.RUnlock() contacts := make([]*Contact, 0, len(ab.contacts)) for _, contact := range ab.contacts { contacts = append(contacts, contact) } return contacts } // Count returns the number of contacts func (ab *AddressBook) Count() int { ab.mu.RLock() defer ab.mu.RUnlock() return len(ab.contacts) } // Clear removes all contacts func (ab *AddressBook) Clear() { ab.mu.Lock() defer ab.mu.Unlock() ab.contacts = make(map[string]*Contact) } // VerifyFingerprint checks if a fingerprint matches a public key func VerifyFingerprint(publicKeyHex, fingerprint string) bool { pubKeyBytes, err := hex.DecodeString(publicKeyHex) if err != nil { return false } if len(pubKeyBytes) < 8 { return false } expectedFingerprint := hex.EncodeToString(pubKeyBytes[:8]) return expectedFingerprint == fingerprint } // String returns a string representation func (ab *AddressBook) String() string { ab.mu.RLock() defer ab.mu.RUnlock() return fmt.Sprintf("AddressBook(contacts=%d)", len(ab.contacts)) }