golang/go · error

ed25519: bad Ed25519ph context length: {l}

Error message

ed25519: bad Ed25519ph context length: {l}

What it means

Returned by SignPH when the context string exceeds 255 bytes. RFC 8032 encodes the context length in a single leading byte, so 255 is the hard ceiling for Ed25519ph and Ed25519ctx.

Source

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

	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()
	// Note that per RFC 8032, Section 5.1, the context SHOULD NOT be empty.
	if l := len(context); l > 255 {
		return nil, errors.New("ed25519: bad Ed25519ctx context length: " + strconv.Itoa(l))

View on GitHub (pinned to b6b368adc5)

Solutions

  1. Keep the context string to <= 255 bytes; ideally a short stable identifier.
  2. If you need more domain separation, hash the longer blob to 32 bytes and use that as the context (or fold it into the message).
  3. Pass an empty context if no context binding is required for Ed25519ph.

Example fix

// before
sig, err := ed25519.SignPH(priv, digest, longPolicyDocument)

// after
ctxID := sha256.Sum256([]byte(longPolicyDocument))
sig, err := ed25519.SignPH(priv, digest, string(ctxID[:]))
Defensive patterns

Strategy: validation

Validate before calling

if len(context) > 255 {
    h := sha256.Sum256([]byte(context))
    context = string(h[:])
}
sig, err := ed25519.SignPH(priv, digest, context)

Try / catch

sig, err := ed25519.SignPH(priv, digest, context)
if err != nil {
    if strings.Contains(err.Error(), "bad Ed25519ph context length") {
        return nil, fmt.Errorf("context too long (%d > 255)", len(context))
    }
    return nil, err
}

Prevention

When it happens

Trigger: Calling fips140/ed25519.SignPH(priv, digest, context) with len(context) > 255 — e.g. passing a full URL, certificate, or arbitrary blob as the context.

Common situations: Stuffing domain-separation metadata, paths, or JSON into the context field; concatenating multiple context values without first hashing them down.

Related errors


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