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

  1. Re-pair with the accessory (run the Pair flow again) to refresh DevicePublic and the session keys.
  2. Verify you are connecting to the correct device: check that mDNS entry Info["id"] matches your DeviceID.
  3. Delete the stale pairing on the accessory (factory reset or remove controller) and pair again.
  4. 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

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


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)