golang/go · error

ed25519: expected opts.HashFunc() zero (unhashed message, fo

Error message

ed25519: expected opts.HashFunc() zero (unhashed message, for standard Ed25519) or SHA-512 (for Ed25519ph)

What it means

Thrown by the default switch arm in ed25519.go:122 (PrivateKey.Sign) when opts.HashFunc() is neither crypto.Hash(0) (unhashed, for Ed25519/ctx) nor crypto.SHA512 (Ed25519ph). Ed25519 does not support arbitrary hash pre-hashing; only those two values are valid.

Source

Thrown at src/crypto/ed25519/ed25519.go:122

		return nil, err
	}
	hash := opts.HashFunc()
	context := ""
	if opts, ok := opts.(*Options); ok {
		context = opts.Context
	}
	switch {
	case hash == crypto.SHA512: // Ed25519ph
		return ed25519.SignPH(k, message, context)
	case hash == crypto.Hash(0) && context != "": // Ed25519ctx
		if fips140only.Enforced() {
			return nil, errors.New("crypto/ed25519: use of Ed25519ctx is not allowed in FIPS 140-only mode")
		}
		return ed25519.SignCtx(k, message, context)
	case hash == crypto.Hash(0): // Ed25519
		return ed25519.Sign(k, message), nil
	default:
		return nil, errors.New("ed25519: expected opts.HashFunc() zero (unhashed message, for standard Ed25519) or SHA-512 (for Ed25519ph)")
	}
}

// Options can be used with [PrivateKey.Sign] or [VerifyWithOptions]
// to select Ed25519 variants.
type Options struct {
	// Hash can be zero for regular Ed25519, or crypto.SHA512 for Ed25519ph.
	Hash crypto.Hash

	// Context, if not empty, selects Ed25519ctx or provides the context string
	// for Ed25519ph. It can be at most 255 bytes in length.
	Context string
}

// HashFunc returns o.Hash.
func (o *Options) HashFunc() crypto.Hash { return o.Hash }

var cryptocustomrand = godebug.New("cryptocustomrand")

View on GitHub (pinned to b6b368adc5)

Solutions

  1. Pass crypto.Hash(0) for standard Ed25519 (sign the raw message), or crypto.SHA512 for Ed25519ph.
  2. When forwarding SignerOpts, branch on the key type and pass ed25519.Options{Hash: crypto.Hash(0)} for Ed25519 keys.
  3. Validate opts.HashFunc() is 0 or SHA-512 before calling Sign on an Ed25519 key.

Example fix

// before
opts := crypto.SHA256 // Ed25519 does not support SHA-256
sig, err := priv.Sign(rand.Reader, msg, opts) // -> error 249

// after
sig, err := priv.Sign(rand.Reader, msg, crypto.Hash(0)) // standard Ed25519
Defensive patterns

Strategy: validation

Validate before calling

hv := opts.HashFunc()
if hv != crypto.Hash(0) && hv != crypto.SHA512 {
    return errors.New("ed25519 requires Hash 0 or SHA-512")
}

Type guard

func validEd25519SignHash(h crypto.Hash) bool {
    return h == crypto.Hash(0) || h == crypto.SHA512
}

Prevention

When it happens

Trigger: Calling priv.Sign with a SignerOpts whose Hash is, e.g., crypto.SHA256 or crypto.BLAKE2b_512 — any hash other than 0 or SHA-512. Often from passing crypto.Hash(c.SHA256) derived from an x509 signature algorithm.

Common situations: Generic signing code that forwards a caller-supplied crypto.SignerOpts; misinterpreting Ed25519 as supporting SHA-256 pre-hashing; plumbing a TLS/JOSE hash identifier into ed25519 Sign.

Related errors


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