slackhq/nebula · error
unable to unmarshal private key: %w
Error message
unable to unmarshal private key: %w
What it means
This error is returned by nistCurve.DH when the local private key bytes cannot be parsed via ecdh.Curve.NewPrivateKey. It indicates the configured local private key is not a valid scalar for the NIST curve — wrong length, all zeros, or out of range. The underlying error is wrapped so the cause is preserved.
Source
Thrown at noiseutil/nist.go:51
if rng == nil {
rng = rand.Reader
}
privkey, err := c.curve.GenerateKey(rng)
if err != nil {
return noise.DHKey{}, err
}
pubkey := privkey.PublicKey()
return noise.DHKey{Private: privkey.Bytes(), Public: pubkey.Bytes()}, nil
}
func (c nistCurve) DH(privkey, pubkey []byte) ([]byte, error) {
ecdhPubKey, err := c.curve.NewPublicKey(pubkey)
if err != nil {
return nil, fmt.Errorf("unable to unmarshal pubkey: %w", err)
}
ecdhPrivKey, err := c.curve.NewPrivateKey(privkey)
if err != nil {
return nil, fmt.Errorf("unable to unmarshal private key: %w", err)
}
return ecdhPrivKey.ECDH(ecdhPubKey)
}
func (c nistCurve) DHLen() int {
// NOTE: Noise Protocol specifies "DHLen" to represent two things:
// - The size of the public key
// - The return size of the DH() function
// But for standard NIST ECDH, the sizes of these are different.
// Luckily, the flynn/noise library actually only uses this DHLen()
// value to represent the public key size, so that is what we are
// returning here. The length of the DH() return bytes are unaffected by
// this value here.
return c.pubLen
}
func (c nistCurve) DHName() string { return c.name }
View on GitHub (pinned to dd8f660c0a)
Solutions
- Regenerate the local key pair with the same library/curve and update the config
- Ensure the private key is the raw scalar (32/48/66 bytes for P-256/P-384/P-521), not a PEM/DER/pkcs8 blob
- Check config parsing: confirm base64 decoding produced the full key (no whitespace truncation)
- Confirm the same curve is used for key generation and DH (e.g. P256 vs P384 mismatch)
Example fix
// before: passing a PEM block's raw bytes as the private key
priv := pemBlock.Bytes
shared, err := curve.DH(priv, pub)
// after: decode to a raw scalar of correct size first
priv, err := base64.StdEncoding.DecodeString(cfg.PrivateKey)
if len(priv) != 32 { return nil, fmt.Errorf("bad P-256 private key length %d", len(priv)) }
shared, err := curve.DH(priv, pub) Defensive patterns
Strategy: validation
Validate before calling
func validNistPriv(curveName string, priv []byte) bool {
var n int
switch curveName {
case "P256": n = 32
case "P384": n = 48
case "P521": n = 66
default: return false
}
if len(priv) != n { return false }
for _, b := range priv { if b != 0 { return true } }
return false // all-zero scalar is invalid
} Type guard
func isPrivUnmarshalError(err error) bool {
return err != nil && strings.Contains(err.Error(), "unable to unmarshal private key")
} Try / catch
shared, err := curve.DH(priv, pub)
if err != nil {
if isPrivUnmarshalError(err) {
return fmt.Errorf("local private key invalid for curve; regenerate keys: %w", err)
}
return err
} Prevention
- Store raw scalars (correct fixed length) in config, not PEM/DER blobs
- Regenerate keys with the same toolchain/curve used at runtime
- Base64-decode and length-check keys at config-load time
- Watch for config parsers trimming or truncating long key strings
When it happens
Trigger: Calling nistCurve.DH(privkey, pubkey) where privkey is empty, the wrong byte length for the curve, or numerically invalid as a private scalar for ecdh.Curve.NewPrivateKey.
Common situations: Hand-edited or base64-decoded config keys with missing/truncated bytes; a key generated for a different curve or algorithm; loading a PEM/DER private key file and passing the raw file bytes instead of the raw scalar.
Related errors
- unable to unmarshal pubkey: %w
- unable to unmarshal pubkey: %w
- got a key of %d bytes, expected %d
- ErrInvalidPEMX25519PrivateKeyBanner
- ErrInvalidPEMEd25519PublicKeyBanner
AI-assisted analysis of slackhq/nebula@dd8f660c0a (2026-09-03).
Data as JSON: /api/errors/33c2b91963977f08.
Report an issue: GitHub.