golang/go · error

ed25519: bad Ed25519ph message hash length: {l}

Error message

ed25519: bad Ed25519ph message hash length: {l}

What it means

Returned by SignPH (Ed25519ph) when the message argument is not exactly sha512Size (64) bytes. Ed25519ph signs a pre-computed SHA-512 hash of the message, so the API contract requires the caller to pass that 64-byte digest, not the raw message.

Source

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

func sign(signature []byte, priv *PrivateKey, message []byte) []byte {
	fipsSelfTest()
	fips140.RecordApproved()
	return signWithDom(signature, priv, message, domPrefixPure, "")
}

func SignPH(priv *PrivateKey, message []byte, context string) ([]byte, error) {
	// Outline the function body so that the returned signature can be
	// stack-allocated.
	signature := make([]byte, signatureSize)
	return signPH(signature, priv, message, context)
}

func signPH(signature []byte, priv *PrivateKey, message []byte, context string) ([]byte, error) {
	fipsSelfTest()
	fips140.RecordApproved()
	if l := len(message); l != sha512Size {
		return nil, errors.New("ed25519: bad Ed25519ph message hash length: " + strconv.Itoa(l))
	}
	if l := len(context); l > 255 {
		return nil, errors.New("ed25519: bad Ed25519ph context length: " + strconv.Itoa(l))
	}
	return signWithDom(signature, priv, message, domPrefixPh, context), nil
}

func SignCtx(priv *PrivateKey, message []byte, context string) ([]byte, error) {
	// Outline the function body so that the returned signature can be
	// stack-allocated.
	signature := make([]byte, signatureSize)
	return signCtx(signature, priv, message, context)
}

func signCtx(signature []byte, priv *PrivateKey, message []byte, context string) ([]byte, error) {
	fipsSelfTest()
	// FIPS 186-5 specifies Ed25519 and Ed25519ph (with context), but not Ed25519ctx.
	fips140.RecordNonApproved()

View on GitHub (pinned to b6b368adc5)

Solutions

  1. Pre-hash the message with SHA-512 and pass the resulting 64-byte digest to SignPH.
  2. If you want the library to hash for you, use the pure Ed25519 Sign instead of Ed25519ph.
  3. Verify interoperability: both signer and verifier must agree on the SHA-512 pre-hash variant.

Example fix

// before
sig, err := ed25519.SignPH(priv, []byte("hello"), "")

// after
digest := sha512.Sum512([]byte("hello"))
sig, err := ed25519.SignPH(priv, digest[:], "")
Defensive patterns

Strategy: validation

Validate before calling

if len(message) != 64 {
    digest := sha512.Sum512(message)
    message = digest[:]
}
sig, err := ed25519.SignPH(priv, message, context)

Try / catch

sig, err := ed25519.SignPH(priv, message, context)
if err != nil {
    if strings.Contains(err.Error(), "bad Ed25519ph message hash length") {
        return nil, fmt.Errorf("SignPH needs a 64-byte SHA-512 digest, got %d bytes", len(message))
    }
    return nil, err
}

Prevention

When it happens

Trigger: Calling fips140/ed25519.SignPH(priv, message, context) where len(message) != 64 — typically because the raw message or a SHA-256 hash was passed instead of a SHA-512 hash.

Common situations: Passing the plaintext message instead of its SHA-512 hash; passing a SHA-256 (32-byte) or SHA-384 hash; assuming SignPH hashes internally (it does not — it expects the digest).

Related errors


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