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[:]...) // KView on GitHub (pinned to b6b368adc5)
Solutions
- Regenerate the semi-expanded bytes from a key produced by NewPrivateKey* via TestingOnlyPrivateKeySemiExpandedBytes.
- Use official NIST/ACVP test vectors verbatim rather than hand-built ones.
- 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
- Never hand-edit individual regions of a semi-expanded key.
- Regenerate blobs from NewPrivateKey* output through the round-trip helper.
- Use untouched NIST/ACVP vectors for conformance tests.
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
- mldsa: semi-expanded private key inconsistent with public ke
- mldsa: invalid semi-expanded private key size
- mldsa: invalid input length for bitUnpackSlow
- mldsa: coefficient out of range
- mldsa: invalid seed length
AI-assisted analysis of golang/go@b6b368adc5 (2026-08-12).
Data as JSON: /api/errors/b7beab25b9e1f370.
Report an issue: GitHub.