package device import ( "crypto/ed25519" "crypto/rand" "encoding/base64" "encoding/hex" "encoding/json" "errors" "os" "path/filepath" ) // Identity 设备身份:Ed25519 密钥对 + 服务器分配的设备 ID type Identity struct { DeviceID uint64 `json:"deviceId"` Name string `json:"name"` PublicKey string `json:"publicKey"` priv ed25519.PrivateKey `json:"-"` } // LoadOrCreate 加载或生成本机设备身份(私钥 hex 文件 0600) func LoadOrCreate(dir string, name string) (*Identity, error) { keyPath := filepath.Join(dir, "device.key") idPath := filepath.Join(dir, "identity.json") if err := os.MkdirAll(dir, 0o700); err != nil { return nil, err } var priv ed25519.PrivateKey if raw, err := os.ReadFile(keyPath); err == nil { seed, err := hex.DecodeString(string(raw)) if err != nil || len(seed) != ed25519.SeedSize { return nil, errors.New("device key corrupted") } priv = ed25519.NewKeyFromSeed(seed) } else { _, priv, err = ed25519.GenerateKey(rand.Reader) if err != nil { return nil, err } if err = os.WriteFile(keyPath, []byte(hex.EncodeToString(priv.Seed())), 0o600); err != nil { return nil, err } } id := &Identity{ Name: name, PublicKey: base64.StdEncoding.EncodeToString(priv.Public().(ed25519.PublicKey)), priv: priv, } if raw, err := os.ReadFile(idPath); err == nil { _ = json.Unmarshal(raw, id) id.priv = priv if id.Name == "" { id.Name = name } } return id, nil } // Save 持久化设备 ID(配对成功后调用) func (i *Identity) Save(dir string) error { raw, err := json.MarshalIndent(i, "", " ") if err != nil { return err } return os.WriteFile(filepath.Join(dir, "identity.json"), raw, 0o600) } // Sign 对载荷签名(hello 握手用) func (i *Identity) Sign(payload []byte) string { return base64.StdEncoding.EncodeToString(ed25519.Sign(i.priv, payload)) }