golang/go · error

mlkem: invalid seed length

Error message

mlkem: invalid seed length

What it means

Thrown by newKeyFromSeed (via NewDecapsulationKey768) when the seed is not exactly SeedSize (64 bytes = 32-byte d || 32-byte z). This is the approved production key-generation path; the seed must be uniformly random and exactly seed-sized.

Source

Thrown at src/crypto/internal/fips140/mlkem/mlkem768.go:205

// exclusively for use in tests.
func GenerateKeyInternal768(d, z *[32]byte) *DecapsulationKey768 {
	fipsSelfTest()
	dk := &DecapsulationKey768{}
	kemKeyGen(dk, d, z)
	return dk
}

// NewDecapsulationKey768 parses a decapsulation key from a 64-byte
// seed in the "d || z" form. The seed must be uniformly random.
func NewDecapsulationKey768(seed []byte) (*DecapsulationKey768, error) {
	// The actual logic is in a separate function to outline this allocation.
	dk := &DecapsulationKey768{}
	return newKeyFromSeed(dk, seed)
}

func newKeyFromSeed(dk *DecapsulationKey768, seed []byte) (*DecapsulationKey768, error) {
	if len(seed) != SeedSize {
		return nil, errors.New("mlkem: invalid seed length")
	}
	d := (*[32]byte)(seed[:32])
	z := (*[32]byte)(seed[32:])
	kemKeyGen(dk, d, z)
	fips140.RecordApproved()
	return dk, nil
}

// TestingOnlyNewDecapsulationKey768 parses a decapsulation key from its expanded NIST format.
//
// Bytes() must not be called on the returned key, as it will not produce the
// original seed.
//
// This function should only be used for ACVP testing. Prefer NewDecapsulationKey768 for all
// other purposes.
func TestingOnlyNewDecapsulationKey768(b []byte) (*DecapsulationKey768, error) {
	if len(b) != decapsulationKeySize768 {
		return nil, errors.New("mlkem: invalid NIST decapsulation key length")

View on GitHub (pinned to b6b368adc5)

Solutions

  1. Read exactly SeedSize (64) bytes from crypto/rand and check the read error.
  2. If persisting a seed, store/transport it as a fixed 64-byte blob and verify length on load.
  3. Do not derive the seed from a password or short entropy source; use the OS CSPRNG.
  4. Distinguish the 64-byte d||z seed from the 2400-byte NIST expanded format handled by TestingOnlyNewDecapsulationKey768.

Example fix

// before
seed := make([]byte, 32) // wrong: only d
 dk, err := mlkem768.NewDecapsulationKey768(seed)
// after
seed := make([]byte, mlkem768.SeedSize)
if _, err := io.ReadFull(crand.Reader, seed); err != nil { return err }
dk, err := mlkem768.NewDecapsulationKey768(seed)
Defensive patterns

Strategy: validation

Validate before calling

if len(seed) != mlkem768.SeedSize {
    return fmt.Errorf("seed len %d != %d", len(seed), mlkem768.SeedSize)
}

Type guard

func isMLKEM768Seed(b []byte) bool { return len(b) == mlkem768.SeedSize }

Try / catch

dk, err := mlkem768.NewDecapsulationKey768(seed)
if err != nil {
    return fmt.Errorf("seed rejected (len=%d): %w", len(seed), err)
}

Prevention

When it happens

Trigger: Passing a 32-byte half-seed, a 96-byte triple, a hex/base64 string instead of raw bytes, or a crypto/rand read that was short due to an ignored error.

Common situations: Confusing SeedSize (64) with the 32-byte d alone, reusing an Ed25519/X25532 seed, feeding a passphrase-derived 32 bytes, or assuming SeedSize matches the NIST expanded format length.

Related errors


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