Crypto recipe
Digital signatures with Ed25519
Sign and verify data with Ed25519 — the modern, footgun-free signature scheme.
A digital signature proves a message came from the holder of a private key, and anyone with the matching public key can verify it. Unlike an HMAC, the verifier needs no secret — which makes signatures the tool for software releases, tokens (JWT), certificates, and any public verification.
Ed25519 is the modern default: small keys, fast, and free of the parameter footguns of RSA and ECDSA. Always verify before you trust signed data.
Get it right
- Prefer Ed25519 (EdDSA). If you must use RSA, use RSA-PSS, not PKCS#1 v1.5.
- Keep the private (signing) key secret; distribute only the public key.
- A signature proves authenticity to anyone — unlike a shared-key MAC.
- Always check the verify result before acting on signed data.
Implementation
Setup Standard library (crypto/ed25519).
package main
import (
"crypto/ed25519"
"crypto/rand"
"fmt"
)
func main() {
pub, priv, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
panic(err)
}
message := []byte("ship release v2.0")
signature := ed25519.Sign(priv, message) // 64 bytes
valid := ed25519.Verify(pub, message, signature)
fmt.Println("valid:", valid)
} Setup pip install cryptography.
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from cryptography.exceptions import InvalidSignature
private_key = Ed25519PrivateKey.generate()
public_key = private_key.public_key()
message = b"ship release v2.0"
signature = private_key.sign(message)
try:
public_key.verify(signature, message) # raises on failure
print("valid")
except InvalidSignature:
print("invalid") Setup Built in (node:crypto; Ed25519 since Node 12).
import { generateKeyPairSync, sign, verify } from 'node:crypto';
const { publicKey, privateKey } = generateKeyPairSync('ed25519');
const message = Buffer.from('ship release v2.0');
// For Ed25519 the algorithm argument is null (the curve fixes the hash).
const signature = sign(null, message, privateKey); // 64 bytes
// verify(algorithm, data, key, signature) — the public key comes before the signature.
const valid = verify(null, message, publicKey, signature); Setup dotnet add package NSec.Cryptography (libsodium-based). .NET has no built-in Ed25519; BouncyCastle is the other common choice.
using NSec.Cryptography;
using System.Text;
var algorithm = SignatureAlgorithm.Ed25519;
using Key key = Key.Create(algorithm);
byte[] message = Encoding.UTF8.GetBytes("ship release v2.0");
byte[] signature = algorithm.Sign(key, message); // 64 bytes
bool valid = algorithm.Verify(key.PublicKey, message, signature); Setup libsodium (crypto_sign is Ed25519).
#include <sodium.h>
#include <string>
int main() {
if (sodium_init() < 0) return 1;
unsigned char pk[crypto_sign_PUBLICKEYBYTES]; // 32
unsigned char sk[crypto_sign_SECRETKEYBYTES]; // 64
crypto_sign_keypair(pk, sk);
std::string msg = "ship release v2.0";
unsigned char sig[crypto_sign_BYTES]; // 64
crypto_sign_detached(sig, nullptr,
reinterpret_cast<const unsigned char *>(msg.data()), msg.size(), sk);
bool valid = crypto_sign_verify_detached(sig,
reinterpret_cast<const unsigned char *>(msg.data()), msg.size(), pk) == 0;
} Setup Built in since Java 15 (EdDSA, JEP 339).
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.Signature;
KeyPairGenerator kpg = KeyPairGenerator.getInstance("Ed25519");
KeyPair pair = kpg.generateKeyPair();
byte[] message = "ship release v2.0".getBytes();
Signature signer = Signature.getInstance("Ed25519");
signer.initSign(pair.getPrivate());
signer.update(message);
byte[] signature = signer.sign();
Signature verifier = Signature.getInstance("Ed25519");
verifier.initVerify(pair.getPublic());
verifier.update(message);
boolean valid = verifier.verify(signature); Setup Cargo.toml: ed25519-dalek = { version = "2", features = ["rand_core"] }, rand = "0.8".
use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
use rand::rngs::OsRng;
fn main() {
let signing_key = SigningKey::generate(&mut OsRng);
let verifying_key: VerifyingKey = signing_key.verifying_key();
let message = b"ship release v2.0";
let signature: Signature = signing_key.sign(message);
let valid = verifying_key.verify(message, &signature).is_ok();
}