OpenNHP/opennhp · critical

private key parse error

Error message

private key parse error %v

What it means

UdpDevice.Start decodes the device's private key from config (PrivateKeyBase64) using base64.StdEncoding before constructing the core device. If the configured string is not valid standard base64 (or empty after trimming), the decode fails, the error is logged, and Start aborts with 'private key parse error <detail>'. The device cannot run without a valid identity key.

Solutions

  1. Regenerate a proper key pair with `nhp-device keygen` and paste the privateKey value into config.toml PrivateKeyBase64
  2. Ensure the value is exact standard base64 with no quotes, whitespace or line breaks
  3. Check that the config template/environment actually rendered the key (no empty ${VAR})
  4. Verify you are editing the config file the process actually loads

Example fix

// before (config.toml)
privateKeyBase64 = ""
// after (config.toml)
privateKeyBase64 = "MC4CAQAwBQYDK2VwBCIEIJ..."  # output of `nhp-device keygen --curve --json`
Defensive patterns

Strategy: validation

Validate before calling

const raw = config.privateKeyBase64;
if (!raw || Buffer.from(raw, 'base64').toString('base64') !== raw) {
  throw new Error('config privateKeyBase64 is missing or not valid standard base64');
}

Type guard

const isValidBase64 = (s) => typeof s === 'string' && s.length > 0 && /^[A-Za-z0-9+/]+={0,2}$/.test(s) && Buffer.from(s, 'base64').toString('base64') === s;

Try / catch

if err := device.Start(); err != nil {
  if strings.HasPrefix(err.Error(), "private key parse error") {
    // regenerate key, fix config, then restart
  }
  return err
}

Prevention

When it happens

Trigger: Starting nhp-db (`nhp-device run` with a config whose PrivateKeyBase64 in config.toml is empty, truncated, contains whitespace/newlines, was generated with URL-safe base64 instead of StdEncoding, or is not base64 at all.

Common situations: Hand-editing config.toml and corrupting the key; pasting a hex key into a base64 field; env-subst templates leaving ${...} unresolved; switching keys generated by different tooling; missing config file defaulting the field to empty.

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


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

Appendix: source

Thrown at endpoints/db/udpdevice.go:139

	// init logger
	a.log = log.NewLogger("NHP-DB", logLevel, filepath.Join(ExeDirPath, "logs"), "device")
	log.SetGlobalLogger(a.log)

	log.Info("=========================================================")
	log.Info("=== NHP-DB %s started                           ===", version.Version)
	log.Info("=== REVISION %s ===", version.CommitId)
	log.Info("=== RELEASE %s                       ===", version.BuildTime)
	log.Info("=========================================================")

	err = a.loadBaseConfig()
	if err != nil {
		return err
	}

	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_DB, prk, nil)
	if a.device == nil {
		log.Critical("failed to create device %v\n", err)
		return fmt.Errorf("failed to create device %v", err)
	}

	a.remoteConnectionMap = make(map[string]*UdpConn)
	a.serverPeerMap = make(map[string]*core.UdpPeer)

	// load peers
	_ = a.loadPeers()

	// load TEEs
	_ = a.loadTEEs()

	a.signals.stop = make(chan struct{})

View on GitHub (pinned to 6e04ca5ff0)