La gema openssl viene con Ruby y envuelve el OpenSSL del sistema: la 3.5.7 en la imagen ruby:4.0.7, con la versión 4.0.2 de la gema. OpenSSL::PKey::EC#sign(digest, data) produce DER ASN.1 con un nonce aleatorio y sin normalización low-S, y verify acepta high-S. dsa_sign_asn1 firma un resumen ya calculado.
Las firmas mal formadas lanzan una excepción en lugar de devolver false: una firma r‖s en bruto de 64 bytes o un blob de 3 bytes lanzaron OpenSSL::PKey::PKeyError: EVP_DigestVerify, mientras que una firma bien formada sobre otro mensaje devolvió false. El código que solo comprueba el booleano termina con una excepción ante una firma al estilo JWS, así que captura PKeyError o convierte antes la firma, como hace el ejemplo; el conversor DER ⇄ en bruto muestra las dos formas una al lado de la otra.
Las claves son inmutables en OpenSSL 3: asignar private_key en una clave EC existente lanzó pkeys are immutable on OpenSSL 3.0, lo que rompe los tutoriales antiguos que construyen una clave asignando el punto o el escalar. En su lugar, construye las claves a partir de DER o PEM: un punto SEC1 suelto tiene que envolverse primero en una estructura SPKI. Fíjate en qué escribe cada método: to_pem en una clave privada escribe SEC1 EC PRIVATE KEY, secreto incluido; private_to_pem escribe PKCS#8, y public_to_pem es el que hay que publicar.
Las curvas disponibles son las de OpenSSL: de P-224 a P-521, secp256k1 y las curvas Brainpool r1, además de Ed25519 y Ed448 mediante OpenSSL::PKey.generate_key. La misma API genérica generó claves ML-DSA-44/65/87 y SLH-DSA, y firmó y verificó con ellas; las claves ML-KEM se pueden generar, pero PKey no ofrece ningún método de encapsulación en la 4.0.2. Para los JWT, ruby-jwt se apoya en esta gema.