package main import ( "encoding/json" "fmt" "os" "sync" ) type credentialStore struct { mu sync.RWMutex path string creds map[string]localCred } func newCredentialStore(path string) (*credentialStore, error) { cs := &credentialStore{ path: path, creds: make(map[string]localCred), } data, err := os.ReadFile(path) if err != nil { if os.IsNotExist(err) { return cs, nil } return nil, fmt.Errorf("read %s: %w", path, err) } if len(data) > 0 { if err := json.Unmarshal(data, &cs.creds); err != nil { return nil, fmt.Errorf("parse %s: %w", path, err) } } return cs, nil } func (cs *credentialStore) persist() error { data, err := json.MarshalIndent(cs.creds, "", " ") if err != nil { return fmt.Errorf("marshal: %w", err) } if err := os.WriteFile(cs.path, data, 0600); err != nil { return fmt.Errorf("write %s: %w", cs.path, err) } return nil } func (cs *credentialStore) Save(id string, publicKey []byte) error { cs.mu.Lock() defer cs.mu.Unlock() if _, exists := cs.creds[id]; exists { return fmt.Errorf("credential already exists") } cs.creds[id] = localCred{PublicKey: publicKey} if err := cs.persist(); err != nil { // roll back so memory matches disk — otherwise the credential looks // registered until restart, then silently vanishes, and a retry hits // "already exists". delete(cs.creds, id) return err } return nil } func (cs *credentialStore) Lookup(id string) (publicKey []byte, signCount int64, err error) { cs.mu.RLock() defer cs.mu.RUnlock() cred, ok := cs.creds[id] if !ok { return nil, 0, fmt.Errorf("credential not found") } return cred.PublicKey, cred.SignCount, nil } func (cs *credentialStore) UpdateSignCount(id string, count int64) error { cs.mu.Lock() defer cs.mu.Unlock() prev, ok := cs.creds[id] if !ok { return fmt.Errorf("credential not found") } cred := prev cred.SignCount = count cs.creds[id] = cred if err := cs.persist(); err != nil { cs.creds[id] = prev // keep memory consistent with disk return err } return nil }