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

  1. Ensure opts.HashFunc() returns a valid crypto.Hash value (e.g., crypto.SHA256) that matches the hash used to produce the digest.
  2. 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.
  3. 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

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


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