OpenNHP/opennhp · critical

failed to create device

Error message

failed to create device %v

What it means

After the private key decodes, Start calls core.NewDevice(core.NHP_DB, prk, nil). NewDevice returns nil when it cannot construct the protocol device (e.g. the decoded key material is rejected at the crypto layer or an internal invariant fails). Start logs a critical message and returns 'failed to create device <err>' so the daemon exits instead of running half-initialized.

Solutions

  1. Regenerate the identity key with `nhp-device keygen` and replace privateKeyBase64 in config.toml
  2. Verify the key length/type matches the device's cipher scheme (SM2 vs Curve25519)
  3. Diff the configured key against the last known-good value in your secret store
  4. Check the accompanying log.Critical output for the underlying device-construction cause

Example fix

// before (config.toml, decodes but invalid key bytes)
privateKeyBase64 = "AAAA"
// after
privateKeyBase64 = "<valid key from `nhp-device keygen --json`>"
Defensive patterns

Strategy: try-catch

Validate before calling

const key = Buffer.from(config.privateKeyBase64, 'base64');
// reject obviously invalid key material before startup
if (key.length === 0 || key.every(b => b === 0)) {
  throw new Error('decoded private key is empty or all zeros; regenerate with nhp-device keygen');
}

Type guard

const isPlausibleKeyMaterial = (b64) => { const b = Buffer.from(b64 ?? '', 'base64'); return b.length >= 32 && !b.every(x => x === 0); };

Try / catch

if err := device.Start(); err != nil {
  if strings.HasPrefix(err.Error(), "failed to create device") {
    log.Fatalf("device init failed (%v): regenerate keys with `nhp-device keygen` and update config", err)
  }
  return err
}

Prevention

When it happens

Trigger: Calling Start (via `nhp-device run`) where core.NewDevice returns nil — typically because the decoded private key bytes are structurally invalid for the device's cipher scheme even though base64 decoding succeeded.

Common situations: A base64 string that decodes but is not a valid SM2/Curve25519 private key (wrong byte length, all zeros, a public key mistakenly configured); keys migrated between cipher schemes; corrupted key files restored from backups.

Related errors


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

Appendix: source

Thrown at endpoints/db/udpdevice.go:145

	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{})
	a.signals.serverMapUpdated = make(chan struct{}, 1)
	a.recvMsgCh = a.device.DecryptedMsgQueue
	a.sendMsgCh = make(chan *core.MsgData, core.SendQueueSize)

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

View on GitHub (pinned to 6e04ca5ff0)