golang/go · error
mldsa: semi-expanded private key inconsistent with public ke
Error message
mldsa: semi-expanded private key inconsistent with public key hash
What it means
After reconstructing t1 from the semi-expanded key, the library re-encodes the public key and recomputes its SHA3-256 'tr' hash; if this hash differs from the tr field embedded in the input, the semi-expanded key is inconsistent with its stated public-key hash. The check catches corruption or hand-editing of ρ/t1/tr regions and is part of the ACVP test parsing path.
Source
Thrown at src/crypto/internal/fips140/mldsa/semiexpanded.go:111
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
sk = append(sk, priv.pub.tr[:]...) // tr
for i := range l {
sk = bitPackSlow(sk, inverseNTT(priv.s1[i]), η, η)
}
for i := range k {
sk = bitPackSlow(sk, inverseNTT(priv.s2[i]), η, η)
}View on GitHub (pinned to b6b368adc5)
Solutions
- Regenerate the whole semi-expanded blob from a NewPrivateKey* key via TestingOnlyPrivateKeySemiExpandedBytes so tr is recomputed.
- Use untouched NIST/ACVP vectors; never patch individual regions of a test key.
- Add a round-trip assertion in the test harness (encode -> decode -> encode) to detect drift.
Example fix
// before
priv, err := mldsa.TestingOnlyNewPrivateKeyFromSemiExpanded(splicedBytes)
// 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 {
ref, _ := mldsa.NewPrivateKey44(seed)
sk = mldsa.TestingOnlyPrivateKeySemiExpandedBytes(ref)
} Prevention
- Keep ρ, t1, and tr consistent by always regenerating them together.
- Use official ACVP vectors; do not splice regions across keys.
- Add encode->decode->encode round-trip assertions in the test harness.
When it happens
Trigger: TestingOnlyNewPrivateKeyFromSemiExpanded where the embedded tr (bytes 64..128) does not match computePublicKeyHash of the reconstructed public key.
Common situations: Editing ρ or t1 without updating tr; truncating the buffer; splicing regions from different keys; using a tr from the wrong hash algorithm.
Related errors
- mldsa: semi-expanded private key inconsistent with t0
- 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/ccd53490df7c30d3.
Report an issue: GitHub.