AlexxIT/go2rtc · error
hap: ValidateSignature
Error message
hap: ValidateSignature
What it means
During Dial's STEP M2, the client verifies the accessory's response signature using the device's long-term ed25519 public key (DevicePublic) over device-LTPK || device-identifier || client-session-public. This error means the signature did not validate: the peer is not the device this client was paired with, or the stored device public key is stale.
Solutions
- Re-pair with the accessory (run the Pair flow again) to refresh DevicePublic and the session keys.
- Verify you are connecting to the correct device: check that mDNS entry Info["id"] matches your DeviceID.
- Delete the stale pairing on the accessory (factory reset or remove controller) and pair again.
- If this recurs unexpectedly, treat it as a security event: confirm no MITM and that the device identity is genuine.
Example fix
// before: stale DevicePublic after device re-pair -> Dial fails
if err := client.Dial(); err != nil {
if strings.Contains(err.Error(), "ValidateSignature") {
// device LTPK changed: re-pair to refresh keys
if err := pairDevice(client); err != nil { return err }
err = client.Dial()
}
}
// after: proactively refresh pairing when the identity mismatch is detected
// (pairDevice runs the M1-M6 setup and updates client.DevicePublic) Defensive patterns
Strategy: validation
Validate before calling
if client.DevicePublic == nil || len(client.DevicePublic) != ed25519.PublicKeySize {
return errors.New("no valid stored device public key; pair the device first")
} Type guard
func hasStoredDeviceKey(c *hap.Client) bool {
return len(c.DevicePublic) == ed25519.PublicKeySize
} Try / catch
if err := client.Dial(); err != nil {
if strings.Contains(err.Error(), "ValidateSignature") {
// device LTPK mismatch: re-pair to refresh DevicePublic
if e := pairDevice(client); e != nil { return e }
return client.Dial()
}
return err
} Prevention
- Refresh stored DevicePublic whenever the accessory is factory-reset or re-paired elsewhere.
- Match the mDNS entry id to DeviceID before connecting to avoid talking to the wrong device.
- Treat validation failures as security events — never proceed with unverified device keys.
When it happens
Trigger: The accessory was factory-reset or re-paired with another controller, so its LTPK no longer matches the stored DevicePublic; connecting to the wrong device that answers on the same mDNS name/IP; a man-in-the-middle impersonating the accessory; corrupted stored pairing data.
Common situations: Camera or hub was reset and re-paired with a different phone; IP address reuse after DHCP reassignment points the client at a different accessory; restoring a client's config backup onto a network with new hardware.
Related errors
- hap: ValidateSignature
- hap: can't dial witout client_id or client_private
- hap: VerifyServerAuthenticator
- echo: bin not in allow_paths:
- exec: bin not in allow_paths:
AI-assisted analysis of AlexxIT/go2rtc@c245815e75 (2026-09-07).
Data as JSON: /api/errors/80ebf39548abb325.
Report an issue: GitHub.
Appendix: source
Thrown at pkg/hap/client.go:171
if err != nil {
return
}
// 3. unpack payload from TLV8
var plainM2 struct {
Identifier string `tlv8:"1"`
Signature string `tlv8:"10"`
}
if err = tlv8.Unmarshal(b, &plainM2); err != nil {
return
}
// 4. verify signature for M2 response with device public
// device session + device id + our session
if c.DevicePublic != nil {
b = Append(cipherM2.PublicKey, plainM2.Identifier, sessionPublic)
if !ed25519.ValidateSignature(c.DevicePublic, b, []byte(plainM2.Signature)) {
return errors.New("hap: ValidateSignature")
}
}
// STEP M3: send our clientID to device
// 1. generate signature with our private key
// (our session + our ID + device session)
b = Append(sessionPublic, c.ClientID, cipherM2.PublicKey)
if b, err = ed25519.Signature(c.ClientPrivate, b); err != nil {
return
}
// 2. generate payload
plainM3 := struct {
Identifier string `tlv8:"1"`
Signature string `tlv8:"10"`
}{
Identifier: c.ClientID,
Signature: string(b),View on GitHub (pinned to c245815e75)