golang/go · error
ecdsa: Sign must be called with a hash, not with crypto.Hash
Error message
ecdsa: Sign must be called with a hash, not with crypto.Hash(0)
What it means
Thrown by PrivateKey.Sign() when opts is non-nil but opts.HashFunc() returns crypto.Hash(0), meaning Hash(0) (no hash). ECDSA signing via the crypto.Signer interface requires a hash to be specified — passing a zero-value Hash indicates the caller forgot to set the hash function or incorrectly claims the data is already hashed without specifying which hash.
Source
Thrown at src/crypto/ecdsa/ecdsa.go:327
// with opts.HashFunc()) using the private key, priv. If the hash is longer than
// the bit-length of the private key's curve order, the hash will be truncated
// to that length. It returns the ASN.1 encoded signature, like [SignASN1].
//
// If random is not nil, the signature is randomized. Most applications should use
// [crypto/rand.Reader] as random, but unless GODEBUG=cryptocustomrand=1 is set, a
// secure source of random bytes is always used, and the actual Reader is ignored.
// The GODEBUG setting will be removed in a future Go release. Instead, use
// [testing/cryptotest.SetGlobalRandom].
//
// If random is nil, Sign will produce a deterministic signature according to RFC
// 6979. When producing a deterministic signature, opts.HashFunc() must be the
// function used to produce digest and priv.Curve must be one of
// [elliptic.P224], [elliptic.P256], [elliptic.P384], or [elliptic.P521].
func (priv *PrivateKey) Sign(random io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) {
if opts != nil {
h := opts.HashFunc()
if h == 0 {
return nil, errors.New("ecdsa: Sign must be called with a hash, not with crypto.Hash(0)")
}
if h.Size() != len(digest) {
return nil, errors.New("ecdsa: hash length does not match hash function")
}
}
if random == nil {
return signRFC6979(priv, digest, opts)
}
random = rand.CustomReader(random)
return SignASN1(random, priv, digest)
}
// GenerateKey generates a new ECDSA private key for the specified curve.
//
// Since Go 1.26, a secure source of random bytes is always used, and the Reader is
// ignored unless GODEBUG=cryptocustomrand=1 is set. This setting will be removed
// in a future Go release. Instead, use [testing/cryptotest.SetGlobalRandom].
func GenerateKey(c elliptic.Curve, r io.Reader) (*PrivateKey, error) {View on GitHub (pinned to b6b368adc5)
Solutions
- Ensure opts.HashFunc() returns a valid crypto.Hash value (e.g., crypto.SHA256) that matches the hash used to produce the digest.
- If the data is not pre-hashed and you want to sign raw bytes, hash them first with the appropriate hash function, then pass matching opts.
- If you truly want deterministic signing, pass opts as a properly configured crypto.SignerOpts (not nil, with a real hash).
Example fix
// before
opts := struct{ hash crypto.Hash }{hash: 0}
sig, err := priv.Sign(rand.Reader, digest, opts)
// after
sig, err := priv.Sign(rand.Reader, digest, crypto.SHA256) // or a SignerOpts returning crypto.SHA256 Defensive patterns
Strategy: validation
Validate before calling
func validateSignerOpts(opts crypto.SignerOpts) error {
if opts == nil { return nil } // nil opts is handled separately
if opts.HashFunc() == 0 {
return errors.New("SignerOpts must specify a non-zero hash")
}
return nil
} Type guard
func hasValidHash(opts crypto.SignerOpts) bool {
return opts == nil || opts.HashFunc() != 0
} Try / catch
sig, err := priv.Sign(rand.Reader, digest, opts)
if err != nil && strings.Contains(err.Error(), "crypto.Hash(0)") {
return fmt.Errorf("must specify a real hash function in SignerOpts: %w", err)
} Prevention
- Always pass a concrete crypto.Hash (e.g., crypto.SHA256) as opts when the digest is pre-hashed.
- Use crypto.Hash values as the opts parameter directly since crypto.Hash implements SignerOpts.
When it happens
Trigger: Calling priv.Sign(rand, digest, opts) where opts is non-nil but opts.HashFunc() == 0. This happens when a custom crypto.SignerOpts implementation returns 0, or when using crypto.Hash(0) explicitly. Note: passing opts == nil skips this check and goes to the RFC 6979 deterministic path (which then requires non-nil opts for the hash).
Common situations: Implementing a custom crypto.SignerOpts that doesn't properly set the hash; passing a zero-value opts struct; incorrect integration with libraries that expect SignerOpts to always specify a real hash; confusing the nil-opts path (deterministic) with the zero-hash path (rejected).
Related errors
- ecdsa: hash length does not match hash function
- ecdsa: hash cannot be empty
- ecdsa: Sign called with nil random and nil opts
- ecdsa: requested hash function unavailable:
- crypto/ecdsa: use of hash functions other than SHA-2 or SHA-
AI-assisted analysis of golang/go@b6b368adc5 (2026-08-12).
Data as JSON: /api/errors/bc6cbf19b7e738f3.
Report an issue: GitHub.