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
- Verify the CipherSuite passed in comes from NewCipherSuite with a valid common.CIPHER_SCHEME_* constant (0=CURVE, 1=GMSM), not a hand-populated struct.
- Check the version of the crypto/hash package backing NewHash for regressions; rebuild from a known-good tag.
- Log the full CipherSuite (HashType, GcmType) at the call site of checkHMAC to confirm the scheme matches the packet header's CipherScheme field.
- 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
- Always build CipherSuites via NewCipherSuite with a documented CIPHER_SCHEME_* constant.
- Add a unit test instantiating NewHash for every supported scheme.
- Never persist or hand-edit HashType/GcmType values in config.
- Fail closed: treat hash-init errors as fatal startup issues, not per-packet retries.
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
- failed to create chain hash
- failed to create HMAC hash
- invalid input key
- missing remote peer public key
- extractInitiatorStaticPubKey: aead
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 onView on GitHub (pinned to 6e04ca5ff0)