golang/go · error

ed25519: bad private key length: {l}

Error message

ed25519: bad private key length: {l}

What it means

Returned by NewPrivateKey when privBytes is not exactly privateKeySize (64 = 32-byte seed + 32-byte public key) bytes. The FIPS PrivateKey type bundles seed and public key as specified by the standard encoding.

Source

Thrown at src/crypto/internal/fips140/ed25519/ed25519.go:114

	s, err := priv.s.SetBytesWithClamping(h[:32])
	if err != nil {
		panic("ed25519: internal error: setting scalar failed")
	}
	A := (&edwards25519.Point{}).ScalarBaseMult(s)
	copy(priv.pub[:], A.Bytes())

	copy(priv.prefix[:], h[32:])
}

func NewPrivateKey(priv []byte) (*PrivateKey, error) {
	p := &PrivateKey{}
	return newPrivateKey(p, priv)
}

func newPrivateKey(priv *PrivateKey, privBytes []byte) (*PrivateKey, error) {
	fips140.RecordApproved()
	if l := len(privBytes); l != privateKeySize {
		return nil, errors.New("ed25519: bad private key length: " + strconv.Itoa(l))
	}

	copy(priv.seed[:], privBytes[:32])

	hs := sha512.New()
	hs.Write(priv.seed[:])
	h := hs.Sum(make([]byte, 0, sha512Size))

	if _, err := priv.s.SetBytesWithClamping(h[:32]); err != nil {
		panic("ed25519: internal error: setting scalar failed")
	}
	// Note that we are not decompressing the public key point here,
	// because it takes > 20% of the time of a signature generation.
	// Signing doesn't use it as a point anyway.
	copy(priv.pub[:], privBytes[32:])

	copy(priv.prefix[:], h[32:])

View on GitHub (pinned to b6b368adc5)

Solutions

  1. Supply exactly 64 raw bytes in the order [32-byte seed][32-byte public key].
  2. If you only have the seed, use NewPrivateKeyFromSeed and then read the public key from the result.
  3. For PKCS#8 PEM inputs, parse with the standard encoding package first and extract the inner 32-byte seed, then call NewPrivateKeyFromSeed.

Example fix

// before: only the 32-byte seed
priv, err := ed25519.NewPrivateKey(seed32)

// after
priv, err := ed25519.NewPrivateKeyFromSeed(seed32)
// or, if you have seed+pub concatenated (64 bytes):
priv, err := ed25519.NewPrivateKey(seedPlusPub64)
Defensive patterns

Strategy: validation

Validate before calling

const ed25519PrivSize = 64
if len(b) != ed25519PrivSize {
    return nil, fmt.Errorf("ed25519 private key must be %d bytes, got %d", ed25519PrivSize, len(b))
}
return ed25519.NewPrivateKey(b)

Try / catch

priv, err := ed25519.NewPrivateKey(b)
if err != nil {
    if strings.Contains(err.Error(), "bad private key length") {
        // caller passed wrong-sized blob; surface clearly
        return nil, fmt.Errorf("invalid ed25519 private key encoding: %w", err)
    }
    return nil, err
}

Prevention

When it happens

Trigger: Calling fips140/ed25519.NewPrivateKey(privBytes) with a byte slice whose length is not 64 — common offenders are 32 (seed only), 96 (some PKCS#8 forms), or arbitrary raw bytes.

Common situations: Confusing the 32-byte seed with the 64-byte private key; passing a PKCS#8 / OpenSSH-wrapped private key without first extracting the raw 64-byte core; passing the 32-byte scalar instead of the seed+pubkey blob.

Related errors


AI-assisted analysis of golang/go@b6b368adc5 (2026-08-12). Data as JSON: /api/errors/0d1c0dada5d6c9fd. Report an issue: GitHub.