OpenNHP/opennhp · error
invalid public key length: got
Error message
invalid public key length: got %d, want 32
What it means
Attestation.verifySm2SignatureWithId verifies an SM2 signature over the quote/attestation data and requires the public key coordinates qx and qy to each be exactly 32 bytes (big-endian, later byte-reversed for the CSV/Huawei format). It throws "invalid public key length: got %d, want 32" when either coordinate is not 32 bytes — typically because the key came from a big.Int.Bytes() that strips leading zero bytes, or the attestation blob was parsed with wrong offsets.
Solutions
- Left-pad each coordinate to 32 bytes before calling: copy into a fixed [32]byte buffer aligned to the end (big-endian) rather than using big.Int.Bytes() directly.
- Verify the offset/length used to slice qx/qy out of the attestation quote matches the CSV report specification for your firmware version.
- Check the quote's own reported key length field and reject malformed reports upstream with a clearer message.
- Confirm the attestation report was not truncated or corrupted in transit (compare sizes against the expected quote structure).
Example fix
// before
qx := pub.X.Bytes() // may be < 32 bytes if leading zeros stripped
attestation.verifySm2SignatureWithId(qx, qy, r, s, id, msg) // "invalid public key length"
// after
func fixed32(n *big.Int) []byte {
b := make([]byte, 32)
nb := n.Bytes()
copy(b[32-len(nb):], nb)
return b
}
attestation.verifySm2SignatureWithId(fixed32(pub.X), fixed32(pub.Y), fixed32(sigR), fixed32(sigS), id, msg) Defensive patterns
Strategy: validation
Validate before calling
func coord32(n []byte) ([]byte, error) {
if len(n) > 32 {
return nil, fmt.Errorf("coordinate too long: %d", len(n))
}
out := make([]byte, 32)
copy(out[32-len(n):], n) // left-pad, big-endian
return out, nil
}
// before calling: qx, err := coord32(pub.X.Bytes()); qy, err := coord32(pub.Y.Bytes()) Type guard
func has32ByteCoords(qx, qy []byte) bool {
return len(qx) == 32 && len(qy) == 32
} Try / catch
if err := attestation.verifySm2SignatureWithId(qx, qy, r, s, id, msg); err != nil {
if strings.Contains(err.Error(), "invalid public key length") {
return fmt.Errorf("attestation pubkey not in fixed 32-byte big-endian form: %w", err)
}
return err
} Prevention
- Never pass big.Int.Bytes() output directly as a fixed-width coordinate; always left-pad to 32 bytes
- Validate quote field offsets against the CSV specification version you target
- Check length fields in the attestation report before slicing qx/qy out of the raw blob
- Add unit tests with keys whose coordinates have leading zero bytes (small X or Y)
When it happens
Trigger: verifyCertChain or Verify extracting the attestation public key from a CSV quote where the coordinate fields were parsed at wrong offsets/lengths, or where a coordinate with leading zero bytes was converted via big.Int.Bytes() (yielding <32 bytes) before being passed in.
Common situations: Parsing quote structures from different TEE/CSV firmware versions with changed field layouts; converting pub.X.Bytes()/pub.Y.Bytes() directly into 32-byte slots without left-padding, so small-coordinate keys (probability ~1/256 per coordinate) fail; hand-crafted or corrupted attestation reports; mixing little-endian raw fields with this function's expectations.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- invalid signature length: got
- size incorrect
- failed to verify signature
- unsupported key type, expect RSA
- JWT signing key is not initialized
AI-assisted analysis of OpenNHP/opennhp@6e04ca5ff0 (2026-09-07).
Data as JSON: /api/errors/09ab50424941e126.
Report an issue: GitHub.
Appendix: source
Thrown at nhp/core/verifier/csv/csv.go:245
// x1, y1 = [r]G
// x2, y2 = [s]PubKey
x1, y1 := pub.Curve.ScalarBaseMult(s.Bytes())
x2, y2 := pub.Curve.ScalarMult(pub.X, pub.Y, t.Bytes())
if x1.Cmp(x2) == 0 && y1.Cmp(y2) == 0 {
// x1, y1 = Double(x1, y1)
x1, y1 = pub.Curve.Double(x1, y1)
} else {
// x1, y1 = x1 + x2, y1 + y2
x1, y1 = pub.Curve.Add(x1, y1, x2, y2)
}
return r.Cmp(new(big.Int).Mod(new(big.Int).Add(x1, e), sm2_N)) == 0
}
func (a *Attestation) verifySm2SignatureWithId(qx, qy, r, s []byte, id []byte, msg []byte) error {
if len(qx) != 32 || len(qy) != 32 {
return fmt.Errorf("invalid public key length: got %d, want 32", len(qx))
}
if len(r) != 32 || len(s) != 32 {
return fmt.Errorf("invalid signature length: got %d, want 32", len(r))
}
qx = ReverseBytes(qx)
qy = ReverseBytes(qy)
r = ReverseBytes(r)
s = ReverseBytes(s)
pubKeyBytes := append(qx, qy...)
pubKeyHex := hex.EncodeToString(pubKeyBytes)
id_msg := buildIDMsg(id, len(id), ECKEY, pubKeyHex)
za, err := Sm3Digest(id_msg)
if err != nil {View on GitHub (pinned to 6e04ca5ff0)