require "openssl" private_key = OpenSSL::PKey.read(File.read("key.pem")) # an OpenSSL::PKey::PKey with oid "ED25519"raise "expected an Ed25519 key, got #{private_key.oid}" unless private_key.oid == "ED25519" message = File.binread("message.txt")signature = private_key.sign(nil, message) # digest nil: Ed25519 hashes internally File.binwrite("sig.bin", signature)puts "signature: #{signature.unpack1("H*")}"require "openssl" public_key = OpenSSL::PKey.read(File.read("pub.pem"))message = File.binread("message.txt")signature = File.binread("sig.bin") puts "valid: #{public_key.verify(nil, signature, message)}"puts "tampered message valid: #{public_key.verify(nil, signature, "hello ed25520")}"How it works
OpenSSL::PKey.readreturns a genericOpenSSL::PKey::PKeyfor Ed25519 — there is no dedicated class likeOpenSSL::PKey::EC— so the code checksoid == "ED25519".sign(nil, message)signs the raw message; thenildigest is mandatory for Ed25519.verify(nil, signature, message)returnstrueorfalse. Note the order: signature before data.File.binreadandFile.binwritekeep the bytes untouched by any encoding conversion.
Gotchas
- A digest name fails:
key.sign("SHA256", data)raisesOpenSSL::PKey::PKeyError: EVP_DigestSignInit: invalid digest (Explicit digest not allowed with EdDSA operations). OpenSSL::PKey::EC.new(pem)does not read Ed25519 keys and raisesOpenSSL::PKey::PKeyError: incorrect pkey type: ED25519. UseOpenSSL::PKey.read.- Raw 32-byte keys load with
OpenSSL::PKey.new_raw_private_key("ED25519", seed)andnew_raw_public_key, and export withraw_private_keyandraw_public_key— available from the openssl gem 3.2, bundled since Ruby 3.3. - Keys and signatures move freely between this code and the
ed25519or RbNaCl gems, but those work with raw 32-byte keys, not PEM — convert at the boundary with the raw-key methods above.