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

  1. Regenerate the whole semi-expanded blob from a NewPrivateKey* key via TestingOnlyPrivateKeySemiExpandedBytes so tr is recomputed.
  2. Use untouched NIST/ACVP vectors; never patch individual regions of a test key.
  3. 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

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


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