The openssl gem ships with Ruby and wraps the system OpenSSL — 3.5.7 in the ruby:4.0.7 image, with gem version 4.0.2. OpenSSL::PKey::EC#sign(digest, data) produces ASN.1 DER with a random nonce and no low-S normalization, and verify accepts high-S. dsa_sign_asn1 signs a digest you already computed.
Malformed signatures raise rather than return false: a 64-byte raw r‖s signature or a 3-byte blob raised OpenSSL::PKey::PKeyError: EVP_DigestVerify, while a well-formed signature over another message returned false. Code that only checks the boolean crashes on a JWS-style signature, so either rescue PKeyError or convert first, as the sample does; the DER ⇄ raw converter shows the two forms side by side.
Keys are immutable on OpenSSL 3: assigning private_key on an existing EC key raised pkeys are immutable on OpenSSL 3.0, which breaks older tutorials that build a key by setting the point or scalar. Construct keys from DER or PEM instead — a bare SEC1 point has to be wrapped in an SPKI structure first. Note which method writes what: to_pem on a private key writes SEC1 EC PRIVATE KEY including the secret, private_to_pem writes PKCS#8, and public_to_pem is the one to publish.
Curve coverage follows OpenSSL: P-224 to P-521, secp256k1 and the Brainpool r1 curves, plus Ed25519 and Ed448 through OpenSSL::PKey.generate_key. The same generic API generated, signed and verified ML-DSA-44/65/87 and SLH-DSA keys; ML-KEM keys can be generated, but PKey offers no encapsulation method in 4.0.2. For JWTs, ruby-jwt builds on this gem.