gastownhall/beads · error
identity: invalid reply MAC
Error message
identity: invalid reply MAC
What it means
VerifyIdentReply authenticates an IdentReply by comparing its MAC against a freshly computed HMAC over the canonical reply, the shared secret, and the request nonce. This error means the reply.MAC field is not valid hex, so it cannot even be decoded for comparison — the peer is not producing protocol-conformant replies.
Source
Thrown at internal/storage/dbproxy/identity/control.go:105
if err != nil {
return IdentReply{}, err
}
payload, err := canonicalIdentReply(reply)
if err != nil {
return IdentReply{}, err
}
mac := hmac.New(sha256.New, []byte(secret))
_, _ = mac.Write(nonceBytes)
_, _ = mac.Write(payload)
reply.MAC = hex.EncodeToString(mac.Sum(nil))
return reply, nil
}
// VerifyIdentReply verifies the authenticated reply for the request nonce.
func VerifyIdentReply(reply IdentReply, secret, nonce string) error {
got, err := hex.DecodeString(reply.MAC)
if err != nil {
return errors.New("identity: invalid reply MAC")
}
signed, err := SignIdentReply(reply, secret, nonce)
if err != nil {
return fmt.Errorf("identity: authenticate reply: %w", err)
}
want, err := hex.DecodeString(signed.MAC)
if err != nil {
return fmt.Errorf("identity: decode expected reply MAC: %w", err)
}
if !hmac.Equal(got, want) {
return errors.New("identity: reply authentication failed")
}
return nil
}
func decodeIdentNonce(nonce string) ([]byte, error) {
raw, err := hex.DecodeString(nonce)
if err != nil || len(raw) != identNonceBytes {View on GitHub (pinned to 71377f2769)
Solutions
- Ensure the peer runs a compatible proxy version producing hex-encoded HMAC-SHA256 MACs
- Treat as an unauthenticated peer: do not trust any reply fields and disconnect
- Verify both sides derive the same 64-char hex secret (see ReadSecret) — encoding mismatches often start at the secret
- Regenerate the proxy secret and re-run discovery if corruption is suspected
Example fix
// before
if err := identity.VerifyIdentReply(reply, secret, nonce); err != nil { return err }
// after
if err := identity.VerifyIdentReply(reply, secret, nonce); err != nil {
if strings.Contains(err.Error(), "invalid reply MAC") { return ErrUntrustedPeer } // reject peer entirely
return err
} Defensive patterns
Strategy: type-guard
Validate before calling
if _, err := hex.DecodeString(reply.MAC); err != nil { return ErrUntrustedPeer } Type guard
func isInvalidMAC(err error) bool { return strings.Contains(err.Error(), "invalid reply MAC") } Try / catch
if err := identity.VerifyIdentReply(reply, secret, nonce); err != nil {
if isInvalidMAC(err) { return ErrUntrustedPeer } // reject, do not trust any fields
return err
} Prevention
- Run compatible proxy versions that emit hex-encoded HMACs
- Never trust reply fields when MAC verification fails
- Keep secrets hex-encoded on both sides to avoid encoding drift
- Regenerate secrets if corruption is suspected
When it happens
Trigger: Calling VerifyIdentReply (directly or via Identify) on a reply whose MAC field contains non-hex characters — empty MAC, base64-encoded MAC, or a tampered/garbage reply from an unauthenticated peer.
Common situations: Rogue process on the control socket replying with arbitrary JSON that happens to decode into IdentReply; protocol version mismatch changing MAC encoding; tests feeding hand-crafted replies.
Related errors
- identity: oversized reply
- identity: reply authentication failed
- ErrMissingBirth
- authenticated proxy identity does not match its pidfile or w
- --allowed-host is empty; pass the Host header value clients
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/a54e16adf9870aa1.
Report an issue: GitHub.