rust-ed25519-compact
A small, self-contained, wasm-friendly Ed25519 and X25519 implementation for Rust with zero mandatory dependencies.
Repository Health
Technical Analysis
ed25519-compact is a Rust crate implementing Ed25519 digital signatures and X25519 key exchange from first principles, with formally-verified Curve25519 field arithmetic at its core. It targets environments where most cryptography crates struggle: no_std embedded systems, WebAssembly modules, and edge compute platforms like Fastly Compute, while remaining a drop-in choice for ordinary server and CLI applications.
The crate has effectively zero dependencies when the calling application supplies its own randomness, and pulls in only getrandom otherwise. Beyond the standard one-shot sign/verify API, it supports incremental (streaming) signing and verification so large messages can be processed in chunks without buffering the whole payload in memory, plus optional PEM import/export, key blinding, and a size-optimized build mode for constrained targets.
Maintained by Frank Denis (the author of libsodium and dnscrypt-proxy), the project has a strong pedigree in applied cryptography and prioritizes correctness and portability over feature breadth, making it a common choice when a project needs Ed25519 without pulling in a large crypto stack.
What You Get
- One-shot
sign()/verify()API for Ed25519 signatures over anyAsRef<[u8]>message - Incremental streaming API (
SigningState/VerifyingState) for signing and verifying large messages in chunks without buffering them - X25519 key exchange support behind the
x25519feature flag - PEM import/export for OpenSSL-compatible key files behind the
pemfeature flag - Key blinding support behind the
blind-keysfeature flag - A
disable-signaturesbuild mode that compiles only the X25519 code path for minimal binary size
Common Use Cases
- Signing and verifying authentication tokens or API requests in a
no_stdembedded firmware target - Implementing Ed25519 signature verification inside a WebAssembly module or Fastly Compute@Edge service
- Streaming-signing large files or payloads without loading the entire message into memory
- Building a minimal-dependency Rust CLI or library that needs Ed25519 without pulling in a full crypto framework
- Performing X25519 Diffie-Hellman key exchange alongside Ed25519 signing in the same crate
Under The Hood
Architecture
The crate is organized as a small set of focused modules under src/: field25519.rs implements the underlying Curve25519 field arithmetic, edwards25519.rs builds Edwards-curve point operations on top of it, ed25519.rs exposes the public PublicKey/SecretKey/Signature/KeyPair types and the one-shot and incremental sign/verify APIs, x25519.rs provides the separate X25519 key-exchange path, and sha512.rs/common.rs/error.rs supply shared hashing, helper types, and a single Error enum used across the crate. lib.rs wires these together behind Cargo feature flags (disable-signatures, x25519, pem, std) using #[cfg] gating, so unused code paths are compiled out entirely rather than dead-code-eliminated at link time — a deliberate design for no_std and size-constrained targets. Sensitive types like SecretKey and the incremental signing/verifying state implement Drop to zero memory on scope exit, and the module boundaries map directly onto the mathematical layers (field arithmetic -> curve arithmetic -> signature scheme), so a change to the field implementation is isolated from the public signing API.
Tech Stack
The crate targets Rust 2018 edition and is no_std-compatible by default, opting into the standard library only behind the std feature. Its dependency footprint is deliberately minimal: getrandom (optional, gated by random) supplies secure randomness including a wasm_js backend for WebAssembly targets, ct-codecs (optional, gated by pem) provides constant-time base64/PEM codecs, and ed25519 (optional, gated by traits) adds interop with the broader ed25519/signature crate ecosystem. There is no build script or code generation step; the field and curve arithmetic are hand-written Rust rather than generated from a spec. A .cargo/config.toml sets getrandom_backend="wasm_js" for wasm32-unknown-unknown builds, reflecting the crate’s explicit WebAssembly and Fastly Compute@Edge deployment targets.
Code Quality
Tests are embedded as #[test] functions directly inside the relevant modules (ed25519.rs, x25519.rs, pem.rs) rather than in a separate tests/ directory, exercising sign/verify round-trips, incremental signing, and PEM parsing. CI (.github/workflows/rust.yml) runs cargo test with default features, a no_std build with --no-default-features, a build with the full optional feature set (pem,traits,self-verify,blind-keys,opt_size), and a disable-signatures-only build, so the feature matrix is exercised on every push rather than assumed to compile. Error handling is explicit throughout: a single Error enum with Display/std::error::Error impls (the latter gated behind std) is returned via Result from all fallible operations instead of panicking. lib.rs carries a short, deliberate list of #[allow(clippy::...)] lints for patterns intrinsic to low-level field arithmetic (single-letter variable names, literal casts), indicating clippy is run as part of the development workflow even though it isn’t wired into CI directly.
API Design
The public API favors small, purpose-built newtypes (PublicKey, SecretKey, Signature, Seed, Noise) that Deref to byte slices, so callers can treat them as raw bytes when needed while still getting type-level guarantees elsewhere. The one-shot path (KeyPair::generate(), sk.sign(), pk.verify()) requires only a few lines to get started, matching the crate’s own README example, and the incremental API mirrors it closely (sign_incremental()/verify_incremental() plus repeated absorb() calls) so streaming usage doesn’t require learning a separate mental model. Optional capabilities (X25519, PEM, blinding, trait interop) are opt-in via Cargo features rather than always-present surface area, which keeps the default API small at the cost of requiring users to know which feature flag unlocks a given method — a reasonable tradeoff for a crate explicitly optimizing for constrained and no_std targets.