{"record":{"id":"ccd53490df7c30d3","repo":"golang/go","slug":"mldsa-semi-expanded-private-key-inconsistent-with-ccd534","errorCode":null,"errorMessage":"mldsa: semi-expanded private key inconsistent with public key hash","messagePattern":"mldsa: semi-expanded private key inconsistent with public key hash","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/crypto/internal/fips140/mldsa/semiexpanded.go","lineNumber":111,"sourceCode":"\tt1 := make([][n]uint16, k, maxK)\n\tfor i := range k {\n\t\ttHat := priv.s2[i]\n\t\tfor j := range l {\n\t\t\ttHat = polyAdd(tHat, nttMul(A[i*l+j], priv.s1[j]))\n\t\t}\n\t\tt := inverseNTT(tHat)\n\t\tfor j := range n {\n\t\t\tr1, r0 := power2Round(t[j])\n\t\t\tt1[i][j] = r1\n\t\t\tif r0 != t0[i][j] {\n\t\t\t\treturn nil, errors.New(\"mldsa: semi-expanded private key inconsistent with t0\")\n\t\t\t}\n\t\t}\n\t}\n\n\tpk := pkEncode(priv.pub.raw[:0], ρ[:], t1, p)\n\tif computePublicKeyHash(pk) != tr {\n\t\treturn nil, errors.New(\"mldsa: semi-expanded private key inconsistent with public key hash\")\n\t}\n\tcomputeT1Hat(priv.t1[:k], t1) // NTT(t₁ ⋅ 2ᵈ)\n\n\treturn priv, nil\n}\n\nfunc TestingOnlyPrivateKeySemiExpandedBytes(priv *PrivateKey) []byte {\n\tk, l, η := priv.pub.p.k, priv.pub.p.l, priv.pub.p.η\n\tsk := make([]byte, 0, semiExpandedPrivKeySize(priv.pub.p))\n\tsk = append(sk, priv.pub.raw[:32]...) // ρ\n\tsk = append(sk, priv.k[:]...)         // K\n\tsk = append(sk, priv.pub.tr[:]...)    // tr\n\tfor i := range l {\n\t\tsk = bitPackSlow(sk, inverseNTT(priv.s1[i]), η, η)\n\t}\n\tfor i := range k {\n\t\tsk = bitPackSlow(sk, inverseNTT(priv.s2[i]), η, η)\n\t}","sourceCodeStart":93,"sourceCodeEnd":129,"githubUrl":"https://github.com/golang/go/blob/b6b368adc57c96c3151d224d172029f233ead2c3/src/crypto/internal/fips140/mldsa/semiexpanded.go#L93-L129","documentation":"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.","triggerScenarios":"TestingOnlyNewPrivateKeyFromSemiExpanded where the embedded tr (bytes 64..128) does not match computePublicKeyHash of the reconstructed public key.","commonSituations":"Editing ρ or t1 without updating tr; truncating the buffer; splicing regions from different keys; using a tr from the wrong hash algorithm.","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."],"exampleFix":"// before\npriv, err := mldsa.TestingOnlyNewPrivateKeyFromSemiExpanded(splicedBytes)\n\n// after\nref, _ := mldsa.NewPrivateKey44(seed)\npriv, err := mldsa.TestingOnlyNewPrivateKeyFromSemiExpanded(\n    mldsa.TestingOnlyPrivateKeySemiExpandedBytes(ref))","handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":"if _, err := mldsa.TestingOnlyNewPrivateKeyFromSemiExpanded(sk); err != nil {\n    ref, _ := mldsa.NewPrivateKey44(seed)\n    sk = mldsa.TestingOnlyPrivateKeySemiExpandedBytes(ref)\n}","preventionTips":["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."],"tags":["crypto","mldsa","fips","testing","consistency"],"backgroundTag":null,"analyzedSha":"b6b368adc57c96c3151d224d172029f233ead2c3","analyzedAt":"2026-08-12T00:22:02.250Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}