OpenNHP/opennhp · error

private key parse error

Error message

private key parse error %v

What it means

The agent's Start decodes config.PrivateKeyBase64 with base64.StdEncoding; if the string is not valid base64 it returns this error instead of creating a device. It guards against corrupted or hand-edited key material producing a broken identity.

Solutions

  1. Regenerate the key with the daemon's keygen --curve --json command and paste the value verbatim
  2. Strip whitespace/newlines/quotes from PrivateKeyBase64
  3. Check the encoding: std base64 required; re-encode any base64url key (strings.ReplaceAll - and _ with + and /) before storing

Example fix

// before
PrivateKeyBase64 = """dJf3
key=="""
// after
PrivateKeyBase64 = "dJf3key=="
Defensive patterns

Strategy: validation

Validate before calling

if _, err := base64.StdEncoding.DecodeString(cfg.PrivateKeyBase64); err != nil {
    return fmt.Errorf("invalid PrivateKeyBase64: %w", err)
}

Try / catch

if err := agent.Start(); err != nil {
    var parseErr error
    if strings.HasPrefix(err.Error(), "private key parse error") {
        log.Fatalf("fix base64 key in config.toml: %v", err)
    }
    _ = parseErr
}

Prevention

When it happens

Trigger: Start (via runApp, runDHPApp, runRegisterApp, RestartAgent) with a PrivateKeyBase64 value containing whitespace, quotes, newlines, hex instead of base64, or a base64url-encoded string with '-'/'_' characters.

Common situations: Copy-pasting a key with surrounding quotes or trailing newline; generating the key with a tool that outputs base64url; manual edits to config.toml mangling the key.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


AI-assisted analysis of OpenNHP/opennhp@6e04ca5ff0 (2026-09-07). Data as JSON: /api/errors/45a48013c6263378. Report an issue: GitHub.

Appendix: source

Thrown at endpoints/agent/udpagent.go:392

	}

	var prk []byte
	if a.config.PrivateKeyBase64 == "" {
		// An empty private key is only acceptable in the register bootstrap
		// flow (allowMissingConfig): use a throwaway key that ReinitWithKey
		// replaces immediately after Start returns. For run/dhp this is a
		// real misconfiguration — fail loudly instead of silently starting
		// with a random key and empty identity.
		if !a.allowMissingConfig {
			log.Error("no private key configured in etc/config.toml")
			return fmt.Errorf("no private key configured; check etc/config.toml")
		}
		prk = core.NewECDH(core.ECC_CURVE25519).PrivateKey()
	} else {
		prk, err = base64.StdEncoding.DecodeString(a.config.PrivateKeyBase64)
		if err != nil {
			log.Error("private key parse error %v\n", err)
			return fmt.Errorf("private key parse error %v", err)
		}
	}

	a.device = core.NewDevice(core.NHP_AGENT, prk, nil)
	if a.device == nil {
		log.Critical("failed to create device %v\n", err)
		return fmt.Errorf("failed to create device %v", err)
	}

	// start device routines
	a.device.Start()

	// serverClusterMap must be non-nil before loadPeers runs (so
	// updateServerPeers' map-swap has a base value) and before
	// callers like AddServer / GetFirstServerPeer touch it on
	// agents that haven't loaded a server.toml yet.
	a.serverClusterMap = make(map[string]*ServerCluster)
	a.serverClusterByName = make(map[string]*ServerCluster)

View on GitHub (pinned to 6e04ca5ff0)