gastownhall/beads · error

identity: invalid request nonce

Error message

identity: invalid request nonce

What it means

decodeIdentNonce hex-decodes a request nonce and requires it to be exactly identNonceBytes (16) raw bytes. SignIdentReply calls it to bind the reply MAC to the request; this error means the nonce string was not valid hex or had the wrong length, so no reply can be signed or verified.

Source

Thrown at internal/storage/dbproxy/identity/control.go:124

	}
	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 {
		return nil, errors.New("identity: invalid request nonce")
	}
	return raw, nil
}

func canonicalIdentReply(reply IdentReply) ([]byte, error) {
	payload := struct {
		Schema      int    `json:"schema"`
		Role        string `json:"role"`
		RootID      string `json:"root_id"`
		UpstreamID  string `json:"upstream_id"`
		PID         int    `json:"pid"`
		Birth       string `json:"birth"`
		DataPort    int    `json:"data_port"`
		ControlPort int    `json:"control_port"`
	}{
		Schema:      reply.Schema,
		Role:        reply.Role,
		RootID:      reply.RootID,

View on GitHub (pinned to 71377f2769)

Solutions

  1. Generate nonces as crypto/rand 16 bytes and hex-encode them (32-char string) on the requester side
  2. Check that the nonce string was not truncated or whitespace-mangled in transit or in config
  3. Use the package's own nonce-generation helpers instead of hand-rolling in tests
  4. If verifying a captured reply, confirm the nonce matches the one sent with the original request

Example fix

// before
nonce := fmt.Sprintf("%x", time.Now().UnixNano()) // wrong size
signed, err := identity.SignIdentReply(reply, secret, nonce)
// after
raw := make([]byte, 16)
rand.Read(raw)
nonce := hex.EncodeToString(raw) // exactly 32 hex chars / 16 bytes
signed, err := identity.SignIdentReply(reply, secret, nonce)
Defensive patterns

Strategy: validation

Validate before calling

func validNonce(n string) bool { raw, err := hex.DecodeString(n); return err == nil && len(raw) == 16 }

Type guard

func isInvalidNonce(err error) bool { return strings.Contains(err.Error(), "invalid request nonce") }

Try / catch

if err != nil {
    if isInvalidNonce(err) { return ErrBugInCaller } // nonce comes from your own code
    return err
}

Prevention

When it happens

Trigger: Calling SignIdentReply with a nonce that is empty, truncated, non-hex, or generated at the wrong size (not 16 random bytes hex-encoded to 32 chars) — e.g. hand-rolled nonce generation in tests or tooling.

Common situations: Custom test harnesses producing non-standard nonces; truncation when logging/serializing the nonce; version mismatch where one side uses a different nonce length.

Related errors


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