{"record":{"id":"a44af39d091b50ba","repo":"gastownhall/beads","slug":"identity-invalid-request-nonce","errorCode":null,"errorMessage":"identity: invalid request nonce","messagePattern":"identity: invalid request nonce","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/dbproxy/identity/control.go","lineNumber":124,"sourceCode":"\t}\n\tsigned, err := SignIdentReply(reply, secret, nonce)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"identity: authenticate reply: %w\", err)\n\t}\n\twant, err := hex.DecodeString(signed.MAC)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"identity: decode expected reply MAC: %w\", err)\n\t}\n\tif !hmac.Equal(got, want) {\n\t\treturn errors.New(\"identity: reply authentication failed\")\n\t}\n\treturn nil\n}\n\nfunc decodeIdentNonce(nonce string) ([]byte, error) {\n\traw, err := hex.DecodeString(nonce)\n\tif err != nil || len(raw) != identNonceBytes {\n\t\treturn nil, errors.New(\"identity: invalid request nonce\")\n\t}\n\treturn raw, nil\n}\n\nfunc canonicalIdentReply(reply IdentReply) ([]byte, error) {\n\tpayload := struct {\n\t\tSchema      int    `json:\"schema\"`\n\t\tRole        string `json:\"role\"`\n\t\tRootID      string `json:\"root_id\"`\n\t\tUpstreamID  string `json:\"upstream_id\"`\n\t\tPID         int    `json:\"pid\"`\n\t\tBirth       string `json:\"birth\"`\n\t\tDataPort    int    `json:\"data_port\"`\n\t\tControlPort int    `json:\"control_port\"`\n\t}{\n\t\tSchema:      reply.Schema,\n\t\tRole:        reply.Role,\n\t\tRootID:      reply.RootID,","sourceCodeStart":106,"sourceCodeEnd":142,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/dbproxy/identity/control.go#L106-L142","documentation":"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.","triggerScenarios":"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.","commonSituations":"Custom test harnesses producing non-standard nonces; truncation when logging/serializing the nonce; version mismatch where one side uses a different nonce length.","solutions":["Generate nonces as crypto/rand 16 bytes and hex-encode them (32-char string) on the requester side","Check that the nonce string was not truncated or whitespace-mangled in transit or in config","Use the package's own nonce-generation helpers instead of hand-rolling in tests","If verifying a captured reply, confirm the nonce matches the one sent with the original request"],"exampleFix":"// before\nnonce := fmt.Sprintf(\"%x\", time.Now().UnixNano()) // wrong size\nsigned, err := identity.SignIdentReply(reply, secret, nonce)\n// after\nraw := make([]byte, 16)\nrand.Read(raw)\nnonce := hex.EncodeToString(raw) // exactly 32 hex chars / 16 bytes\nsigned, err := identity.SignIdentReply(reply, secret, nonce)","handlingStrategy":"validation","validationCode":"func validNonce(n string) bool { raw, err := hex.DecodeString(n); return err == nil && len(raw) == 16 }","typeGuard":"func isInvalidNonce(err error) bool { return strings.Contains(err.Error(), \"invalid request nonce\") }","tryCatchPattern":"if err != nil {\n    if isInvalidNonce(err) { return ErrBugInCaller } // nonce comes from your own code\n    return err\n}","preventionTips":["Generate nonces with crypto/rand: 16 bytes, hex-encoded (32 chars)","Use the package's nonce helpers instead of hand-rolling","Never truncate or whitespace-trim nonce strings in logs/config round-trips","Keep nonce size consistent across protocol versions"],"tags":["identity","nonce","cryptography","validation"],"backgroundTag":"invalid-nonce","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}