openssl gem は Ruby に同梱されており、システムの OpenSSL をラップします(ruby:4.0.7 イメージでは OpenSSL 3.5.7、gem のバージョンは 4.0.2)。OpenSSL::PKey::EC#sign(digest, data) はランダムな nonce で ASN.1 DER の署名を生成し、low-S 正規化は行いません。verify は high-S を受け入れます。dsa_sign_asn1 は計算済みのダイジェストに署名します。
形式の誤った署名は false を返すのではなく例外を発生させます。64 バイトの生の r‖s 署名や 3 バイトのデータでは OpenSSL::PKey::PKeyError: EVP_DigestVerify が発生し、形式が正しく別のメッセージに対する署名では false が返りました。真偽値だけをチェックするコードは JWS 形式の署名でクラッシュするため、PKeyError を捕捉するか、サンプルのように先に変換してください。DER ⇄ 生形式の変換ツールでは 2 つの形式を並べて確認できます。
OpenSSL 3 では鍵はイミュータブルです。既存の EC 鍵に private_key を代入すると pkeys are immutable on OpenSSL 3.0 が発生するため、点やスカラーを設定して鍵を組み立てる古いチュートリアルは動きません。代わりに DER や PEM から鍵を構築してください。SEC1 の点そのものは、先に SPKI 構造でラップする必要があります。どのメソッドが何を書き出すかにも注意してください。秘密鍵に対する to_pem は秘密の値を含む SEC1 の EC PRIVATE KEY を書き出し、private_to_pem は PKCS#8 を書き出します。公開すべきなのは public_to_pem の出力です。
対応する曲線は OpenSSL に準じ、P-224〜P-521、secp256k1、Brainpool の r1 曲線に加え、OpenSSL::PKey.generate_key で Ed25519 と Ed448 も使えます。同じ汎用 API で ML-DSA-44/65/87 と SLH-DSA の鍵の生成、署名、検証ができました。ML-KEM の鍵は生成できますが、4.0.2 の PKey にはカプセル化のメソッドがありません。JWT には、この gem を基盤とする ruby-jwt を使います。