OpenNHP/opennhp · error

extractInitiatorStaticPubKey: chain hash

Error message

extractInitiatorStaticPubKey: chain hash: %w

What it means

extractInitiatorStaticPubKey builds a throwaway chain hash (InitialHash || serverPubKey || ephemeral) to recover the initiator's static public key during checkHMAC. This error wraps a NewHash failure for the cipher suite's hash type, meaning the responder cannot even begin verifying the message.

Solutions

  1. Align the responder's cipher scheme configuration with the initiator's (both CURVE or both GMSM)
  2. Validate ciphers.HashType is set from a supported scheme constant
  3. Call NewHash(hashType) in an init check to fail fast at startup
  4. Rebuild with complete crypto backends if hash algorithms were trimmed

Example fix

// before
responder, _ := nhpcore.NewDevice(nhpcore.NHP_SERVER)
responder.SetCiphers(&nhpcore.CipherSuites{}) // empty suite
// after
responder.SetCiphers(nhpcore.NewCipherSuites(nhpcore.CIPHER_SCHEME_CURVE))
Defensive patterns

Strategy: validation

Validate before calling

if _, err := nhpcore.NewHash(ciphers.HashType); err != nil {
    return fmt.Errorf("responder hash type invalid: %w", err)
}

Type guard

func hashReady(c *nhpcore.CipherSuites) bool {
    h, err := nhpcore.NewHash(c.HashType)
    return err == nil && h != nil
}

Try / catch

ppd, err := responder.PacketToMsg(pkt)
if err != nil && strings.Contains(err.Error(), "chain hash") {
    log.Errorf("cipher mismatch with initiator: %v", err)
    return
}

Prevention

When it happens

Trigger: checkHMAC (nhp/core/responder.go:140) invoked during packet verification with ciphers.HashType that NewHash cannot instantiate (unknown algorithm, zero value, or missing crypto backend).

Common situations: Responder configured with a different/unknown cipher scheme than the peer; uninitialized CipherSuites in a custom code path; builds lacking SM3 (GMSM) support.

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/25a2ea66e4fb009f. Report an issue: GitHub.

Appendix: source

Thrown at nhp/core/responder.go:140

// instances; doing it here too costs one extra ECDH per RKN-under-
// overload packet, but keeps the noise-transcript machinery untouched
// (and untouchable from this helper, which only takes a *Device, the
// cipher suite, and the header).
func extractInitiatorStaticPubKey(device *Device, ciphers *CipherSuite, header Header) ([]byte, error) {
	deviceEcdh := device.GetEcdhByCipherScheme(header.CipherScheme())

	ess := deviceEcdh.SharedSecret(header.EphermeralBytes())
	if ess == nil {
		return nil, ErrDeviceECDHEphermalFailed
	}
	defer SetZero(ess[:])

	// Local chain hash: InitialHash || serverPubKey || ephemeral
	// (mirrors validatePeer's ChainHash0 → ChainHash1 evolution, but
	// 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))

View on GitHub (pinned to 6e04ca5ff0)