Skip to content
ecdsa.com

Ed25519 sign & verify · Rust

How to sign and verify Ed25519 signatures in Rust

In Rust, Ed25519 signatures come from the ed25519-dalek crate. With its pkcs8 and pem features it reads the same PEM files as OpenSSL, sign returns the deterministic RFC 8032 signature, and verify_strict adds the checks that protocols needing exactly one valid signature per message rely on.

Tested with ed25519-dalek 3.0.0 (Rust 1.99.1).

Cargo.toml — ed25519-dalek with PEM support
[dependencies]
ed25519-dalek = { version = "=3.0.0", features = ["pkcs8", "pem"] }
hex = "=0.4.3"
src/main.rs — load the PEM keys, sign and verify
use ed25519_dalek::pkcs8::{DecodePrivateKey, DecodePublicKey};
use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
fn main() -> Result<(), Box<dyn std::error::Error>> {
// PKCS#8 "BEGIN PRIVATE KEY" and SPKI "BEGIN PUBLIC KEY"
let signing_key = SigningKey::from_pkcs8_pem(&std::fs::read_to_string("key.pem")?)?;
let verifying_key = VerifyingKey::from_public_key_pem(&std::fs::read_to_string("pub.pem")?)?;
let message = std::fs::read("message.txt")?;
let signature: Signature = signing_key.sign(&message);
std::fs::write("sig.bin", signature.to_bytes())?;
println!("signature: {}", hex::encode(signature.to_bytes()));
// verify_strict also rejects small-order keys and non-canonical signatures
let received = Signature::from_slice(&std::fs::read("sig.bin")?)?;
println!("valid: {}", verifying_key.verify_strict(&message, &received).is_ok());
println!("tampered message valid: {}", verifying_key.verify(b"hello ed25520", &received).is_ok());
Ok(())
}

How it works

  1. The pkcs8 feature brings the DecodePrivateKey and DecodePublicKey traits; pem adds the from_pkcs8_pem and from_public_key_pem constructors used here.
  2. SigningKey::sign comes from the Signer trait and returns a Signature; to_bytes() gives the 64-byte encoding every other language uses.
  3. Signature::from_slice parses received bytes and returns an error for a wrong length instead of panicking.
  4. verify_strict also rejects weak (small-order) public keys and non-canonical signatures that plain verify accepts; prefer it unless you must accept everything other implementations accept.

Gotchas

  • Many examples online target ed25519-dalek 1.x, whose Keypair, SecretKey and PublicKey types were renamed to SigningKey and VerifyingKey in 2.0; they no longer compile. Pin the version, as the =3.0.0 above does.
  • SigningKey::from_bytes takes the 32-byte seed. A 64-byte libsodium-style secret key goes through SigningKey::from_keypair_bytes, which also checks that the second half is the matching public key (Mismatched Keypair detected otherwise).
  • With only the pkcs8 feature, the build fails with no associated function or constant named from_pkcs8_pem found for struct SigningKey — an error that points at your code, not at Cargo.toml. The PEM constructors need the pem feature as well.
  • Signatures from this crate are deterministic, so tests may compare bytes against a fixture — but the same test against a randomized implementation such as Apple CryptoKit must verify instead.

Related recipes