golang/go · error

mldsa: semi-expanded private key inconsistent with t0

Error message

mldsa: semi-expanded private key inconsistent with t0

What it means

When reconstructing a private key from the semi-expanded form, the library recomputes the public polynomial t1 from s1/s2 and uses power2Round to derive t0; if the derived t0 does not match the t0 supplied in the byte blob, the key is internally inconsistent and errInvalidSignature-style 'inconsistent with t0' is returned. It is a cross-check that guards against a corrupted or hand-edited semi-expanded key.

Source

Thrown at src/crypto/internal/fips140/mldsa/semiexpanded.go:104

	//  > return values that are not in the correct range. Hence, skDecode
	//  > should only be run on inputs that come from trusted sources.
	//
	// so it sounds like it doesn't even want us to check the coefficients are
	// within bounds, but especially if using this format for key exchange, that
	// sounds like a bad idea. So we check everything.

	t1 := make([][n]uint16, k, maxK)
	for i := range k {
		tHat := priv.s2[i]
		for j := range l {
			tHat = polyAdd(tHat, nttMul(A[i*l+j], priv.s1[j]))
		}
		t := inverseNTT(tHat)
		for j := range n {
			r1, r0 := power2Round(t[j])
			t1[i][j] = r1
			if r0 != t0[i][j] {
				return nil, errors.New("mldsa: semi-expanded private key inconsistent with t0")
			}
		}
	}

	pk := pkEncode(priv.pub.raw[:0], ρ[:], t1, p)
	if computePublicKeyHash(pk) != tr {
		return nil, errors.New("mldsa: semi-expanded private key inconsistent with public key hash")
	}
	computeT1Hat(priv.t1[:k], t1) // NTT(t₁ ⋅ 2ᵈ)

	return priv, nil
}

func TestingOnlyPrivateKeySemiExpandedBytes(priv *PrivateKey) []byte {
	k, l, η := priv.pub.p.k, priv.pub.p.l, priv.pub.p.η
	sk := make([]byte, 0, semiExpandedPrivKeySize(priv.pub.p))
	sk = append(sk, priv.pub.raw[:32]...) // ρ
	sk = append(sk, priv.k[:]...)         // K

View on GitHub (pinned to b6b368adc5)

Solutions

  1. Regenerate the semi-expanded bytes from a key produced by NewPrivateKey* via TestingOnlyPrivateKeySemiExpandedBytes.
  2. Use official NIST/ACVP test vectors verbatim rather than hand-built ones.
  3. If mutating test data intentionally, re-encode through the round-trip helper to keep regions consistent.

Example fix

// before
priv, err := mldsa.TestingOnlyNewPrivateKeyFromSemiExpanded(editedBytes)

// after
ref, _ := mldsa.NewPrivateKey44(seed)
priv, err := mldsa.TestingOnlyNewPrivateKeyFromSemiExpanded(
    mldsa.TestingOnlyPrivateKeySemiExpandedBytes(ref))
Defensive patterns

Strategy: validation

Try / catch

if _, err := mldsa.TestingOnlyNewPrivateKeyFromSemiExpanded(sk); err != nil {
    // regenerate the blob from a known-good key instead of patching it
    ref, _ := mldsa.NewPrivateKey44(seed)
    sk = mldsa.TestingOnlyPrivateKeySemiExpandedBytes(ref)
}

Prevention

When it happens

Trigger: TestingOnlyNewPrivateKeyFromSemiExpanded with bytes where the embedded s1/s2 and t0 regions do not correspond (one was edited without the other).

Common situations: Test vectors assembled by hand instead of from a conformant key; bytes mutated between semi-expanded encoding and decoding; mixing regions from two different keys.

Related errors


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