OpenNHP/opennhp · error
invalid signature length: got
Error message
invalid signature length: got %d, want 32
What it means
Attestation.verifySm2SignatureWithId requires the signature components r and s to each be exactly 32 bytes, because they are byte-reversed and assembled into the SM2 verification equation per GB/T 32918. It throws "invalid signature length: got %d, want 32" when either r or s is not 32 bytes — typically a DER-encoded or ASN.1-style signature, or a minimal-length big.Int encoding that dropped leading zero bytes.
Solutions
- Ensure the signature is in raw 64-byte r||s form: if you have DER, parse the ASN.1 SEQUENCE and extract r and s individually, then left-pad each to 32 bytes.
- Left-pad r and s to exactly 32 bytes (big-endian) before calling, instead of using big.Int.Bytes() output directly.
- Verify the offsets used to slice r and s from the attestation quote/structure match the expected 32+32 layout.
- Log len(r) and len(s) at the call site to identify whether one component or both are malformed, then fix the producer side accordingly.
Example fix
// before rBytes := sigR.Bytes() // 31 bytes when r has a leading zero -> "invalid signature length" rBytes = bigIntTo32(sigR) // 32-byte left-padded, byte-reversed sBytes = bigIntTo32(sigS) attestation.verifySm2SignatureWithId(qx, qy, rBytes, sBytes, id, msg)
Defensive patterns
Strategy: validation
Validate before calling
func sigComponent32(n *big.Int) []byte {
b := make([]byte, 32)
nb := n.Bytes()
copy(b[32-len(nb):], nb) // left-pad to fixed 32 bytes, big-endian
return b
}
// before calling: r := sigComponent32(sigR); s := sigComponent32(sigS) Type guard
func isRaw64ByteSig(sig []byte) bool {
return len(sig) == 64 // raw r||s form; DER signatures (~70-72 bytes) must be converted first
} Try / catch
if err := attestation.verifySm2SignatureWithId(qx, qy, r, s, id, msg); err != nil {
if strings.Contains(err.Error(), "invalid signature length") {
return fmt.Errorf("signature must be fixed 32-byte r and s (raw r||s, not DER): %w", err)
}
return err
} Prevention
- Convert DER/ASN.1 ECDSA signatures to raw 64-byte r||s before feeding the CSV verifier
- Always left-pad r and s to 32 bytes rather than relying on big.Int.Bytes() minimal encoding
- Confirm the 32/32 split offsets when extracting r and s from an attestation quote
- Test verification with signatures where r or s has leading zero bytes to catch padding bugs early
When it happens
Trigger: verifyCertChain or Verify passing signature bytes decoded from a DER/ASN.1 SEQUENCE(r,s) blob (variable length, ~70 bytes) instead of the raw 64-byte r||s form, or splitting a 64-byte raw signature at a wrong offset, or using big.Int.Bytes() on r/s without left-padding to 32 bytes.
Common situations: Signatures produced by standard Go crypto/ecdsa SignAsn1 or OpenSSL (DER) fed into the CSV verifier expecting fixed-width 32-byte components; signatures extracted from quotes with off-by-one offsets; small r or s values losing leading zero bytes when serialized via big.Int.Bytes(); corrupted attestation payloads.
Understand the failure class
Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.
Related errors
- invalid public key 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/80ea06696de74909.
Report an issue: GitHub.
Appendix: source
Thrown at nhp/core/verifier/csv/csv.go:249
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 {
return err
}
msgAll := append(za, msg...)View on GitHub (pinned to 6e04ca5ff0)