slackhq/nebula · error
unable to unmarshal pubkey: %w
Error message
unable to unmarshal pubkey: %w
What it means
This error is returned by nistCurve.DH in noiseutil when the peer's public key bytes cannot be parsed as a valid public key on the configured NIST curve (P-256/P-384/P-521) via ecdh.Curve.NewPublicKey. It means the Noise handshake received a remote static/ephemeral public key that is not a valid, canonical point on the expected curve. The underlying ecdh error is wrapped with %w so the root cause is preserved.
Source
Thrown at noiseutil/nist.go:47
}
}
func (c nistCurve) GenerateKeypair(rng io.Reader) (noise.DHKey, error) {
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.View on GitHub (pinned to dd8f660c0a)
Solutions
- Verify both peers use the same NIST curve (e.g. 'P256' in the Noise config) so key lengths match
- Regenerate or re-export the offending key pair with the same library used for parsing (crypto/ecdh)
- Check that the public key bytes are the full uncompressed point size for the curve (65/97/133 bytes) and not a truncated or DER-encoded copy
- Inspect the wrapped %w cause with errors.Unwrap/errors.As to distinguish length vs point-validity failures
Example fix
// before: mixing curves across peers
key, _ := nistCurve{curve: ecdh.P256()}.DH(priv, peerP384Pub) // fails to unmarshal
// after: ensure both sides use the same curve
if len(pub) != expectedPubLen(curveName) {
return nil, fmt.Errorf("peer pubkey wrong size for %s", curveName)
}
key, err := nistCurve{curve: ecdh.P256()}.DH(priv, pub) Defensive patterns
Strategy: validation
Validate before calling
func validNistPub(curveName string, pub []byte) bool {
var n int
switch curveName {
case "P256": n = 65
case "P384": n = 97
case "P521": n = 133
default: return false
}
return len(pub) == n && pub[0] == 4
} Type guard
func isEcdhUnmarshalError(err error) bool {
var e *ecdh.PublicKey // NewPublicKey failure is generic error; check wrapped text
_ = e
return err != nil && strings.Contains(err.Error(), "unable to unmarshal pubkey")
} Try / catch
shared, err := curve.DH(priv, pub)
if err != nil {
var cause error
errors.As(err, &cause)
return fmt.Errorf("handshake DH failed (check peer curve/key bytes): %w", err)
} Prevention
- Pin the same Noise curve on every peer in your deploy config
- Validate peer key byte length against the curve before handshakes
- Never hand-edit base64 keys; always generate via the library's keygen
- Log wrapped causes with errors.Unwrap when debugging key issues
When it happens
Trigger: Calling nistCurve.DH(privkey, pubkey) with pubkey bytes that are empty, the wrong length for the curve, or not a valid EC point — e.g. a remote peer configured with a key pair generated on a different curve or with a non-NIST key format.
Common situations: Mismatched curve configuration between peers (one on P-256, other on P-384); corrupted or truncated keys stored in config/certs; keys generated by a different library with a different encoding; a peer sending garbage during a handshake attempt.
Related errors
- unable to unmarshal private key: %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/b80b2d70829c2ced.
Report an issue: GitHub.