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
- Verify the agent's keypair type (curve vs sm2 from keygen) matches the cipher scheme stamped into outgoing headers.
- Regenerate agent keys with the matching keygen flag (./nhp-agentd keygen --sm2 for GMSM) and redeploy.
- Keep agent and server on the same release so PublicKeySizeEx and the scheme table agree.
- 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
- Generate keys with the keygen flag matching your scheme (--sm2 for GMSM).
- Validate key length at daemon startup against the configured scheme.
- Render config templates with the scheme-consistent key fields.
- Pin agent and server to the same version.
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
- extractInitiatorStaticPubKey: curve scheme expected
- size incorrect
- missing connection data for server
- missing remote address
- keepalive packet size is incorrect
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 int64View on GitHub (pinned to 6e04ca5ff0)