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

  1. Ensure the peer runs a compatible proxy version producing hex-encoded HMAC-SHA256 MACs
  2. Treat as an unauthenticated peer: do not trust any reply fields and disconnect
  3. Verify both sides derive the same 64-char hex secret (see ReadSecret) — encoding mismatches often start at the secret
  4. 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

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


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/a54e16adf9870aa1. Report an issue: GitHub.