Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Digital Signatures: Ed25519, ECDSA, RSA

🔐 Used in: TLS certificates, SSH, software updates, package signing, blockchains, identity systems

✅ A digital signature is public proof that this exact message came from the holder of a private key.

Digital signatures solve a problem encryption does not:

  • Encryption answers: “Who can read this?”
  • Signatures answer: “Who wrote this, and was it changed?”

If you can verify a signature, you can trust:

  • Authenticity: the signer had the private key
  • Integrity: the message was not modified

That is why signatures are the backbone of:

  • certificate chains1
  • secure updates
  • authenticated key exchange2 (e.g., TLS handshake signatures)
  • “verify before you run” security3

The Basic Model: sign → verify

In almost every signature scheme, you can think like this:

signature = Sign(private_key, message)
ok = Verify(public_key, message, signature)

Anyone can verify. Only the private-key holder can sign.

Hash-Then-Sign (why hashes show up everywhere)

Signatures are almost never computed over “big messages” directly.

Instead, protocols use:

digest = Hash(message)
signature = Sign(private_key, digest)

Why?

  • Hashes are fixed-size (fast to sign, easy to transport)
  • Hashes bind the entire message (one-bit change → totally different digest)
  • Collision resistance4 matters: signatures inherit the security of the hash

This is why you’ll constantly see “SHA-256 + ECDSA” or “SHA-256 + RSA-PSS”.

Ed25519: modern default for new systems

My Crate Logo Crate used: ed25519-dalek

Ed25519 is an elliptic-curve5 signature scheme designed to be:

  • fast
  • hard to misuse
  • deterministic (no fragile per-signature randomness like classic ECDSA)

The name “Ed25519” refers to an Edwards-curve signature scheme using the Curve25519 family with a 255-bit prime field.

It’s an excellent default when you control both ends of a system (new protocol, internal services, new products).

🧪 Code Example: Ed25519 (source code)

#![allow(unused)]
fn main() {
pub fn run_ed25519_example() {
    use ed25519_dalek::{Signer, SigningKey, Verifier};
    use rand::rngs::OsRng;

    let signing_key = SigningKey::generate(&mut OsRng);
    let verifying_key = signing_key.verifying_key();

    let message = b"sealed-in-rust: ed25519 signature demo";
    let signature = signing_key.sign(message);

    verifying_key.verify(message, &signature).unwrap();
    println!("Ed25519 signature verified.");
}
}

Output:

Ed25519 signature verified.

🚨 Critical rule: Always verify signatures with the library’s verification API (constant-time comparisons6 and correct checks7). Never compare signatures manually.

ECDSA: widely deployed, easy to get wrong

My Crate Logo Crate used: p256

ECDSA is the “classic” elliptic-curve signature family used everywhere (TLS, hardware tokens, many enterprise systems). ECDSA stands for Elliptic Curve Digital Signature Algorithm (DSA adapted to elliptic curves).

The danger in ECDSA is the per-signature secret nonce k:

  • If k is ever reused → the private key can be recovered
  • If k is biased/predictable → the private key can leak

Many libraries use deterministic nonces8 (RFC 6979) to reduce RNG foot-guns9, but the easiest safe rule is: use well-audited libraries and don’t customize nonce generation.

🧪 Code Example: ECDSA (source code)

#![allow(unused)]
fn main() {
pub fn run_ecdsa_example() {
    use p256::ecdsa::Signature;
    use p256::ecdsa::SigningKey;
    use p256::ecdsa::signature::{Signer, Verifier};
    use rand::rngs::OsRng;

    let signing_key = SigningKey::random(&mut OsRng);
    let verifying_key = signing_key.verifying_key();

    let message = b"sealed-in-rust: ecdsa(p-256) signature demo";
    let signature: Signature = signing_key.sign(message);

    verifying_key.verify(message, &signature).unwrap();
    println!("ECDSA P-256 signature verified.");
}
}

Output:

ECDSA P-256 signature verified.

RSA signatures: compatibility workhorse (use RSA-PSS)

My Crate Logo Crate used: rsa

RSA is older than ECC10, but it’s still deeply embedded in the world (certificate ecosystems, legacy devices, enterprise).

Important RSA signature reality:

  • Raw RSA is not a signature scheme
  • You must use a padding scheme designed for signatures

Today, that means:

  • Prefer RSA-PSS11 (modern, safer)
  • Avoid “textbook RSA”12 entirely

🧪 Code Example: RSA (source code)

#![allow(unused)]
fn main() {
pub fn run_rsa_example() {
    use rand::rngs::OsRng;
    use rsa::RsaPrivateKey;
    use rsa::pss::{BlindedSigningKey, VerifyingKey};
    use rsa::signature::{RandomizedSigner, Verifier};
    use sha2::Sha256;

    let mut rng = OsRng;
    let private_key = RsaPrivateKey::new(&mut rng, 2048).unwrap();
    let public_key = private_key.to_public_key();

    let signing_key = BlindedSigningKey::<Sha256>::new(private_key);
    let verifying_key = VerifyingKey::<Sha256>::new(public_key);

    let message = b"sealed-in-rust: rsa-pss signature demo";
    let signature = signing_key.sign_with_rng(&mut rng, message);

    verifying_key.verify(message, &signature).unwrap();
    println!("RSA-PSS signature verified.");
}
}

Output:

RSA-PSS signature verified.

Which one should you choose?

If you’re designing something new: Ed25519 is usually the best default

If you need broad TLS13 / enterprise / hardware ecosystem compatibility: ECDSA (P-256) is common

If you must support legacy systems or existing PKI deployments14: RSA (prefer PSS) still shows up everywhere

Common Mistakes (how signatures fail in real life)

  • Confusing signatures with encryption: signing does not hide data; it proves origin and integrity.
  • Verifying the wrong thing: always verify the exact bytes that were signed (canonical encoding matters).
  • Algorithm confusion: never accept “any algorithm”; allow-list what you expect (JWT-style mistakes15 are common)
  • Bad randomness (ECDSA): nonces are a single-point-of-failure
  • Using outdated RSA modes: “RSA without padding” is broken; “RSA-PSS” is the modern baseline

🟢 Conclusion

Digital signatures are the public-key primitive for authenticity. Hash-then-sign binds meaning, not just bytes.

Ed25519 is the modern default.

ECDSA is everywhere but fragile around nonces.

RSA is still common, but only with the right padding (prefer PSS).


  1. Certificate chain: ordered set of certificates where each certificate is signed by the next/higher authority up to a trusted root, used to bind a public key to an identity. More

  2. Authenticated key exchange (AKE): key exchange that also authenticates the peer (via signatures, certificates, or PSKs), preventing active man-in-the-middle attacks. More

  3. Verify-before-run security: practice of verifying a signature on code or artifacts (updates, packages, binaries) before installing or executing them. More

  4. Collision: two different inputs producing the same hash; collision resistance is critical for signature security. More

  5. Elliptic curve: the mathematical structure used by ECC; curves over finite fields provide the group operations behind ECDSA and Ed25519. More

  6. Constant-time comparison: comparing values (MACs, signatures, hashes) without early exits so timing does not leak information about secret-dependent equality. More

  7. Correct checks (verification hygiene): strict signature verification rules like algorithm allow-lists, parameter validation, and rejecting malformed/malleable signatures. More

  8. Deterministic nonce: deriving the per-signature nonce from the private key and message (e.g., RFC 6979) to reduce dependence on external randomness at signing time. More

  9. RNG foot-gun: common randomness mistake (weak seeding, predictability, reuse) that silently breaks cryptography, especially nonce-based signatures like ECDSA. More

  10. ECC (elliptic-curve cryptography): public-key cryptography built on elliptic-curve groups, typically giving smaller keys and faster operations than RSA at comparable security. More

  11. RSA-PSS: the modern RSA signature padding scheme (“Probabilistic Signature Scheme”), designed to be robust against signature forgeries and recommended over older RSA signature modes. More

  12. Textbook RSA: raw RSA without a padding scheme; deterministic and malleable, and therefore insecure for both encryption and signatures. More

  13. Broad TLS compatibility: choosing signature algorithms/parameters that work across a wide range of TLS clients, servers, libraries, and devices (often constrained by legacy support). More

  14. PKI deployment: the real-world operational certificate ecosystem (CAs, issuance policies, revocation, client trust stores) that shapes which algorithms can be used in practice. More

  15. JWT (JSON Web Token): signed token format; secure usage requires strict algorithm allow-lists and proper signature verification. More