package node import ( "crypto/ed25519" "crypto/rand" "crypto/sha256" "encoding/hex" "encoding/json" "errors" "os" "path/filepath" "strings" "sync" "unicode" "unicode/utf8" ) type State struct { Version int `json:"version"` PrivateKey []byte `json:"private_key"` Name string `json:"name"` } type Store struct { mu sync.Mutex path string state State } func OpenStore(dir string) (*Store, error) { if err := os.MkdirAll(dir, 0700); err != nil { return nil, err } s := &Store{path: filepath.Join(dir, "identity.json")} b, err := os.ReadFile(s.path) if err == nil { if err = json.Unmarshal(b, &s.state); err != nil { return nil, errors.New("invalid identity; recovery required") } if s.state.Version != 1 || len(s.state.PrivateKey) != ed25519.PrivateKeySize { return nil, errors.New("unsupported identity; recovery required") } derived := ed25519.NewKeyFromSeed(s.state.PrivateKey[:ed25519.SeedSize]) if !equalKey(derived, s.state.PrivateKey) { return nil, errors.New("corrupt identity; recovery required") } if info, e := os.Stat(s.path); e != nil || info.Mode().Perm()&0077 != 0 { return nil, errors.New("identity permissions must be private") } return s, nil } if !errors.Is(err, os.ErrNotExist) { return nil, err } _, key, err := ed25519.GenerateKey(rand.Reader) if err != nil { return nil, err } s.state = State{Version: 1, PrivateKey: key, Name: "Моя нода"} if err := s.write(s.state); err != nil { return nil, err } return s, nil } func equalKey(a, b []byte) bool { return string(a) == string(b) } func (s *Store) write(state State) error { b, err := json.Marshal(state) if err != nil { return err } f, err := os.CreateTemp(filepath.Dir(s.path), ".identity-*") if err != nil { return err } defer os.Remove(f.Name()) if _, err = f.Write(b); err != nil { f.Close() return err } if err = f.Sync(); err != nil { f.Close() return err } if err = f.Close(); err != nil { return err } if err = os.Rename(f.Name(), s.path); err != nil { return err } d, err := os.Open(filepath.Dir(s.path)) if err != nil { return err } defer d.Close() return d.Sync() } func (s *Store) Public() (string, string) { s.mu.Lock() defer s.mu.Unlock() pub := ed25519.PrivateKey(s.state.PrivateKey).Public().(ed25519.PublicKey) hash := sha256.Sum256(pub) return "node_" + hex.EncodeToString(hash[:]), s.state.Name } func (s *Store) Rename(name string) error { name = strings.TrimSpace(name) if name == "" || !utf8.ValidString(name) || utf8.RuneCountInString(name) > 64 || strings.ContainsFunc(name, unicode.IsControl) { return errors.New("Название должно содержать от 1 до 64 символов без управляющих знаков") } s.mu.Lock() defer s.mu.Unlock() next := s.state next.Name = name if err := s.write(next); err != nil { return errors.New("Не удалось сохранить название") } s.state = next return nil }