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

  1. 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.
  2. Left-pad r and s to exactly 32 bytes (big-endian) before calling, instead of using big.Int.Bytes() output directly.
  3. Verify the offsets used to slice r and s from the attestation quote/structure match the expected 32+32 layout.
  4. 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

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


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)