OpenNHP/opennhp · error

extractInitiatorStaticPubKey: init hash

Error message

extractInitiatorStaticPubKey: init hash: %w

What it means

extractInitiatorStaticPubKey is the responder-side helper that re-derives the Noise IK key chain to decrypt the initiator's static public key from the packet header. This error wraps a failure from NewHash, i.e. the configured hash algorithm (BLAKE2s for CURVE, SM3 for GMSM) could not be instantiated. In practice this only happens if the CipherSuite's HashType is invalid or the crypto backend failed to initialize, since both supported hash types are always compiled in.

Solutions

  1. Verify the CipherSuite passed in comes from NewCipherSuite with a valid common.CIPHER_SCHEME_* constant (0=CURVE, 1=GMSM), not a hand-populated struct.
  2. Check the version of the crypto/hash package backing NewHash for regressions; rebuild from a known-good tag.
  3. Log the full CipherSuite (HashType, GcmType) at the call site of checkHMAC to confirm the scheme matches the packet header's CipherScheme field.
  4. If a custom cipher scheme was added, implement its HashType in NewHash/NewCipherSuite before routing packets with that scheme.

Example fix

// before
suite := &core.CipherSuite{HashType: unknownHash, GcmType: unknownGcm}
ppd.checkHMAC(sumCookie) // extractInitiatorStaticPubKey: init hash: ...
// after
suite := core.NewCipherSuite(common.CIPHER_SCHEME_CURVE) // valid scheme
ppd.checkHMAC(sumCookie)
Defensive patterns

Strategy: validation

Validate before calling

if suite.HashType == 0 || (suite.HashType != hashCurve && suite.HashType != hashGmsm) {
	return fmt.Errorf("unsupported hash type %d for cipher suite", suite.HashType)
}

Type guard

func isValidCipherSuite(c *core.CipherSuite) bool {
	return c != nil && (c.GcmType == common.CIPHER_SCHEME_CURVE || c.GcmType == common.CIPHER_SCHEME_GMSM)
}

Try / catch

peerPk, err := extractInitiatorStaticPubKey(dev, suite, hdr)
if err != nil {
	log.Error("static key extraction failed: %v", err)
	return ErrServerHMACCheckFailed // fail closed, never bypass the cookie path
}

Prevention

When it happens

Trigger: Calling checkHMAC on an RKN packet under server overload when ciphers.HashType holds an unsupported value — e.g. a CipherSuite constructed with a scheme value outside CIPHER_SCHEME_CURVE/CIPHER_SCHEME_GMSM, or a crypto provider regression that makes hash instantiation fail.

Common situations: A hand-built CipherSuite passed into device code with a zero/garbage HashType; a build with a modified crypto backend where the SM3 or BLAKE2s implementation fails to register; effectively never hit with the stock two supported cipher schemes.

Understand the failure class

Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.

Related errors


AI-assisted analysis of OpenNHP/opennhp@6e04ca5ff0 (2026-09-07). Data as JSON: /api/errors/716522da723907cc. Report an issue: GitHub.

Appendix: source

Thrown at nhp/core/responder.go:155

	// in a throwaway hash that never leaks back to ppd).
	chainHash, err := NewHash(ciphers.HashType)
	if err != nil {
		return nil, fmt.Errorf("extractInitiatorStaticPubKey: chain hash: %w", err)
	}
	chainHash.Write([]byte(InitialHashString))
	chainHash.Write(deviceEcdh.PublicKey())
	chainHash.Write(header.EphermeralBytes())

	// Local chain key: ChainKey0 = MixKey(InitialHash, InitialChainKey)
	// then ChainKey0 → ChainKey1 via ess.
	var noise NoiseFactory
	noise.HashType = ciphers.HashType
	var chainKey [SymmetricKeySize]byte
	defer SetZero(chainKey[:])
	// ChainKey0
	initHash, err := NewHash(ciphers.HashType)
	if err != nil {
		return nil, fmt.Errorf("extractInitiatorStaticPubKey: init hash: %w", err)
	}
	initHash.Write([]byte(InitialHashString))
	noise.MixKey(&chainKey, initHash.Sum(nil), []byte(InitialChainKeyString))
	// ChainKey0 → ChainKey1
	noise.MixKey(&chainKey, chainKey[:], header.EphermeralBytes())

	// Derive AEAD key for static-field decryption.
	var key [SymmetricKeySize]byte
	defer SetZero(key[:])
	noise.KeyGen2(&chainKey, &key, chainKey[:], ess[:])

	aead, err := AeadFromKey(ciphers.GcmType, &key)
	if err != nil {
		return nil, fmt.Errorf("extractInitiatorStaticPubKey: aead: %w", err)
	}
	// Trust the AEAD's returned plaintext length over a static
	// scheme→size mapping. The previous version allocated a
	// PublicKeySizeEx-sized buffer and sliced it back down based on

View on GitHub (pinned to 6e04ca5ff0)