golang/go · error

ecdsa: private key scalar is zero or negative

Error message

ecdsa: private key scalar is zero or negative

What it means

Thrown by privateKeyToFIPS when converting a legacy crypto/ecdsa PrivateKey to its FIPS nistec form. The guard checks priv.D.Sign() <= 0, rejecting a private scalar that is zero or negative. A valid ECDSA private key must be a positive integer in [1, N-1]; zero/negative is either corrupted input, a mis-encoded key, or an uninitialized (*big.Int)(nil)-adjacent value.

Source

Thrown at src/crypto/ecdsa/ecdsa.go:602

		return nil, err
	}
	return ecdsa.NewPublicKey(c, Q)
}

var privateKeyCache fips140cache.Cache[PrivateKey, ecdsa.PrivateKey]

func privateKeyToFIPS[P ecdsa.Point[P]](c *ecdsa.Curve[P], priv *PrivateKey) (*ecdsa.PrivateKey, error) {
	Q, err := pointFromAffine(priv.Curve, priv.X, priv.Y)
	if err != nil {
		return nil, err
	}

	// Reject values that would not get correctly encoded.
	if priv.D.BitLen() > priv.Curve.Params().N.BitLen() {
		return nil, errors.New("ecdsa: private key scalar too large")
	}
	if priv.D.Sign() <= 0 {
		return nil, errors.New("ecdsa: private key scalar is zero or negative")
	}

	size := (priv.Curve.Params().N.BitLen() + 7) / 8
	const maxScalarSize = 66 // enough for a P-521 private key
	if size > maxScalarSize {
		return nil, errors.New("ecdsa: internal error: curve size too large")
	}
	D := priv.D.FillBytes(make([]byte, size, maxScalarSize))

	return privateKeyCache.Get(priv, func() (*ecdsa.PrivateKey, error) {
		return ecdsa.NewPrivateKey(c, D, Q)
	}, func(k *ecdsa.PrivateKey) bool {
		return subtle.ConstantTimeCompare(k.PublicKey().Bytes(), Q) == 1 &&
			subtle.ConstantTimeCompare(k.Bytes(), D) == 1
	})
}

// pointFromAffine is used to convert the PublicKey to a nistec SetBytes input.

View on GitHub (pinned to b6b368adc5)

Solutions

  1. Verify priv.D is set and in the range [1, N-1] before calling Sign/SignASN1; load keys only via x509.ParseECPrivateKey / ParsePKCS8PrivateKey.
  2. Regenerate the key with ecdsa.GenerateKey if the source scalar is corrupt.
  3. If loading from raw bytes, build the key with D.SetBytes and validate it is non-zero and below the curve order N.

Example fix

// before
priv := new(ecdsa.PrivateKey)
priv.D = big.NewInt(0) // corrupt/empty
sig, err := ecdsa.SignASN1(rand.Reader, priv, hash) // -> error 240

// after
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil { return err }
sig, err := ecdsa.SignASN1(rand.Reader, priv, hash)
Defensive patterns

Strategy: validation

Validate before calling

if priv.D == nil || priv.D.Sign() <= 0 {
    return errors.New("private key scalar must be positive")
}
N := priv.Curve.Params().N
if priv.D.Cmp(N) >= 0 {
    return errors.New("private key scalar must be < curve order")
}

Type guard

func validPrivateKeyScalar(priv *ecdsa.PrivateKey) bool {
    return priv != nil && priv.D != nil && priv.D.Sign() > 0 && priv.D.Cmp(priv.Curve.Params().N) < 0
}

Try / catch

sig, err := ecdsa.SignASN1(rand.Reader, priv, hash)
if err != nil {
    if strings.Contains(err.Error(), "private key scalar is zero or negative") {
        // key is corrupt; regenerate or reject
    }
    return err
}

Prevention

When it happens

Trigger: Called from ecdsa.Sign / SignASN1 / SignReader (ecdsa.go:301,425,459) when priv.D is zero or has a negative sign. Happens when a PrivateKey is constructed by hand with priv.D = big.NewInt(0), when a key is parsed from malformed encoding that yields a zero/invalid scalar, or when unmarshaling logic leaves D unset and a zero default is passed.

Common situations: Importing a private key from a corrupt PEM/DER file, deserializing a key from an untrusted source, copy-paste errors that zero out D, or test fixtures that forgot to populate the scalar.

Related errors


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