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

  1. 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.
  2. Verify the offset/length used to slice qx/qy out of the attestation quote matches the CSV report specification for your firmware version.
  3. Check the quote's own reported key length field and reject malformed reports upstream with a clearer message.
  4. 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

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


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)