OpenNHP/opennhp · error

extractInitiatorStaticPubKey: gmsm scheme expected

Error message

extractInitiatorStaticPubKey: gmsm scheme expected %d-byte pubkey, got %d

What it means

Mirror of the CURVE case: after AEAD-opening the static field, the plaintext must be exactly PublicKeySizeEx (65 bytes for SM2) when the header declares CIPHER_SCHEME_GMSM. A different length means the decrypted bytes are not an SM2 public key — typically the initiator encrypted a Curve25519 key while marking the header GMSM, or a nonstandard plaintext length was produced.

Solutions

  1. Verify the agent's keypair type (curve vs sm2 from keygen) matches the cipher scheme stamped into outgoing headers.
  2. Regenerate agent keys with the matching keygen flag (./nhp-agentd keygen --sm2 for GMSM) and redeploy.
  3. Keep agent and server on the same release so PublicKeySizeEx and the scheme table agree.
  4. Add a startup self-check on the agent asserting len(ownPublicKey) == expected size for its configured scheme.

Example fix

// before
./nhp-agentd keygen --curve   # 32-byte key, but header says GMSM
// after
./nhp-agentd keygen --sm2     # 65-byte key matching CIPHER_SCHEME_GMSM
Defensive patterns

Strategy: validation

Validate before calling

if cfg.CipherScheme == common.CIPHER_SCHEME_GMSM && len(agentPublicKey) != core.PublicKeySizeEx {
	return fmt.Errorf("GMSM scheme requires %d-byte SM2 key, agent key is %d bytes", core.PublicKeySizeEx, len(agentPublicKey))
}

Type guard

func isSm2Key(pk []byte) bool { return len(pk) == core.PublicKeySizeEx }

Try / catch

if err := validateKeyAgainstScheme(cfg.Scheme, cfg.PrivateKey); err != nil {
	log.Fatal(err) // misconfiguration — fail at startup, not per packet
}

Prevention

When it happens

Trigger: Agent built with curve keys but configured/flagged to use the GMSM scheme; custom cipher implementations whose ciphertext decrypts to a non-65-byte payload; sender/receiver version skew where PublicKeySizeEx changed.

Common situations: Deployments where agents run --curve keygen output but the packet header claims GMSM; forks with an additional scheme reusing the GMSM constant; config templates rendering mismatched key material into agent config.

Related errors


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

Appendix: source

Thrown at nhp/core/responder.go:196

	// however many bytes the AEAD decrypted, then the caller would
	// either truncate them or hash trailing zero-padding).
	//
	// Validate the length explicitly before returning so future
	// breakage manifests as an error here, not as cookie failures
	// further down. Pass nil for the dst so Open allocates exactly
	// the right size.
	peerPk, err := aead.Open(nil, header.NonceBytes(), header.StaticBytes(), chainHash.Sum(nil))
	if err != nil {
		return nil, fmt.Errorf("extractInitiatorStaticPubKey: open: %w", err)
	}
	switch header.CipherScheme() {
	case common.CIPHER_SCHEME_CURVE:
		if len(peerPk) != PublicKeySize {
			return nil, fmt.Errorf("extractInitiatorStaticPubKey: curve scheme expected %d-byte pubkey, got %d", PublicKeySize, len(peerPk))
		}
	case common.CIPHER_SCHEME_GMSM:
		if len(peerPk) != PublicKeySizeEx {
			return nil, fmt.Errorf("extractInitiatorStaticPubKey: gmsm scheme expected %d-byte pubkey, got %d", PublicKeySizeEx, len(peerPk))
		}
	default:
		return nil, fmt.Errorf("extractInitiatorStaticPubKey: unknown cipher scheme %d (pubkey length %d)", header.CipherScheme(), len(peerPk))
	}
	return peerPk, nil
}

type ResponderScheme interface {
	CreatePacketParserData(d *Device, pd *PacketData) (ppd *PacketParserData, err error)
	DerivePacketParserDataFromPrevAssemblerData(mad *MsgAssemblerData, pkt *Packet, initTime int64) (ppd *PacketParserData)
	validatePeer(d *Device, ppd *PacketParserData) (err error)
	decryptBody(d *Device, ppd *PacketParserData) (err error)
}

type CookieStore struct {
	CurrCookie     [CookieSize]byte
	PrevCookie     [CookieSize]byte
	LastCookieTime int64

View on GitHub (pinned to 6e04ca5ff0)