OpenSSL provides libcrypto and libssl for C and, through bindings, for Node.js, Python, PHP and Ruby, plus the openssl command most people meet first. The facts here were checked on the Alpine 3.24.2 distribution package, OpenSSL 3.5.9 (the 3.5 long-term-support line); the newest upstream releases at the time were 3.6.5 and 4.0.3, so behaviour may differ in those.
ECDSA signatures are ASN.1 DER — 70 to 72 bytes on P-256 — from EVP_DigestSign and from openssl dgst -sign. Nonces are random unless you set the nonce-type parameter to 1 (OpenSSL 3.2+), which reproduced RFC 6979's P-256 test vector; on the command line that parameter is only reachable through pkeyutl, as the sample shows. s is not normalized and verification accepts high-S. EVP_PKEY_sign signs a digest you computed.
Malformed input is an error, not a false result: EVP_DigestVerify returned -1 for a raw 64-byte r‖s signature, and the CLI said so. openssl dgst -verify printed Verified OK for a good signature, Verification failure for a valid signature over other data, and Error verifying data for 64 bytes that are not DER — so in 3.5.9 a format problem can be told apart from a forgery by the wording. Convert raw signatures with the DER ⇄ raw converter, or let the OpenSSL command builder assemble the right invocation.
Curves include P-224 to P-521, secp256k1 and the Brainpool r1 curves, plus Ed25519 and Ed448. Since 3.5 the default provider also implements ML-KEM, ML-DSA and SLH-DSA, and the TLS hybrid group X25519MLKEM768. Keys come as PKCS#8 (plain or encrypted) or SEC1 private keys and SPKI or certificate public keys: genpkey writes -----BEGIN PRIVATE KEY-----, the legacy openssl ec writes -----BEGIN EC PRIVATE KEY----- — a file labelled EC is always a private key.
Most error messages people search for come from key loading — no start line, header too long, unsupported from the decoder — and are covered on the linked error pages.