GnuTLS provides TLS, X.509 and PKCS handling for many GNU and Linux components and ships certtool and gnutls-cli. Its abstract gnutls_privkey_* and gnutls_pubkey_* interfaces work the same for software keys, PKCS#11 tokens and TPMs. The facts here were checked on the Alpine 3.24.2 package, GnuTLS 3.8.13, which uses Nettle for the arithmetic. Since GnuTLS 3.8.10, post-quantum algorithms come only from the optional leancrypto library, and this package does not link it.
gnutls_privkey_sign_data returns ECDSA signatures as DER with a random nonce; the flag GNUTLS_PRIVKEY_FLAG_REPRODUCIBLE switches to RFC 6979, and with it the RFC's P-256 test vector reproduced exactly. s is not normalized and high-S signatures verify. gnutls_privkey_sign_hash2 signs a digest you computed.
Verification returns a negative error code on failure, and every kind of failure looked the same: a raw r‖s signature, a 3-byte blob and a valid signature over other data all gave GNUTLS_E_PK_SIG_VERIFY_FAILED (-89). Use the DER ⇄ raw converter to rule out an encoding problem.
ECDSA is limited to the NIST curves P-224 to P-521 — gnutls_ecc_curve_get_id("SECP256K1") returned 0, unknown — while Ed25519 and Ed448 both signed and verified. The headers declare GNUTLS_PK_MLKEM768 and GNUTLS_PK_MLDSA65, but this build implements neither: they are missing from gnutls_pk_list(), ML-DSA-65 key generation fails with GNUTLS_E_UNSUPPORTED_SIGNATURE_ALGORITHM, and the TLS groups listed by gnutls-cli --list include X25519 and X448 but no ML-KEM hybrid. Code that compiles against these constants can still fail at run time, so check the runtime list. Inspect certificates produced by certtool with the certificate decoder.