cargo : curve25519-dalek @ 4.1.3
PE Patrick Elsen signed 2026-05-28 published 2026-05-28

Claims

build-exec-deterministicbuild-exec-minimalbuild-exec-no-networkbuild-exec-no-write-outbuild-exec-safecrypto-impl-testedhas-binarieshas-build-exechas-fuzz-testshas-install-exechas-integration-testshas-property-testshas-unit-testsimpl-algorithmimpl-concurrencyimpl-cryptoimpl-datastructureimpl-interpreterimpl-jitimpl-parserimpl-protocolis-benignunsafe-documentedunsafe-minimalunsafe-safeuses-concurrencyuses-cryptouses-environmentuses-execuses-filesystemuses-interpreteruses-jituses-networkuses-unsafe

Summary

curve25519-dalek 4.1.3 implements Curve25519 / ristretto255 group arithmetic for X25519 and Ed25519. 37 unsafe sites, almost all AVX2/AVX512-IFMA intrinsics in the SIMD backend gated by cpufeatures CPUID dispatch, were reviewed and hold; no I/O at run time. No findings. Field-arithmetic correctness and constant-time resistance were scoped out and left unasserted.

Report

Subject

curve25519-dalek 4.1.3 is a pure-Rust, no_std implementation of group operations on Curve25519 and ristretto255, the elliptic-curve arithmetic underlying X25519 key agreement and Ed25519 signatures. It exposes four public types: Scalar (arithmetic mod the group order l = 2^252 + 27742317777372353535851937790883648493), EdwardsPoint and MontgomeryPoint (the two forms of Curve25519), and RistrettoPoint (the prime-order ristretto255 group), along with constants and traits modules. It is a foundational dependency of ed25519-dalek, x25519-dalek, and much of the Rust cryptography ecosystem. Source is roughly 32K LOC across src/.

Methodology

Tooling: openvet 0.6.0, ripgrep/grep, diff, the Read tool, and manual review. I read Cargo.toml, Cargo.toml.orig, build.rs, src/lib.rs, src/backend/mod.rs, src/backend/vector/packed_simd.rs, and the SIMD field-arithmetic files in full, and surveyed field.rs, scalar.rs, edwards.rs, ristretto.rs, and montgomery.rs. I enumerated every unsafe site (37 total) and read each one.

The shipped crate omits a vcs/ checkout, so byte-for-byte diff against a VCS tree was not possible. Integrity was instead checked against the workspace manifest.json, which records a per-file blake2b-256 digest for all 60 packaged files plus a whole-archive digest; .cargo_vcs_info.json records upstream git sha1 5312a0311ec40df95be953eacfa8a11b9a34bc54. The contents/ tree matches the manifest.

Scope. This is a cryptographic crate whose field-arithmetic correctness and side-channel resistance need dedicated cryptographic review. The following claims were not evaluated and are left unasserted; they must not be read as either satisfied or violated: crypto-impl-safe, crypto-impl-correct. The crate documents constant-time intent (per-operation # Preconditions/# Postconditions bounds on limb sizes, pervasive use of subtle) but this audit does not independently vouch for constant-time behavior or field-arithmetic correctness. This audit verifies supply-chain integrity, the capability surface, build-time execution, the unsafe surface, and dependency enumeration.

Results

The contents/ tree matches every per-file digest in manifest.json; no source-file divergence or unexpected binaries were found, and the crate contains no obfuscated payloads, base64 blobs, network endpoints, or telemetry, so it is benign (is-benign). The crate ships only Rust source plus docs, a Makefile, a vendor/ristretto.sage reference script, and tests/build_tests.sh, with no precompiled assets (has-binaries).

The capability surface is narrow. No std::net, std::fs, std::process, or std::env appears in src/, giving uses-network, uses-filesystem, uses-exec, and uses-environment; the only environment reads are in build.rs at build time. There is no threading or shared-mutable state (uses-concurrency), and no JIT or interpreter (uses-jit, uses-interpreter). The crate implements elliptic-curve group arithmetic and uses subtle constant-time primitives throughout, supporting uses-crypto, impl-crypto, and impl-algorithm. It is not a parser, protocol, interpreter, JIT, data structure, or concurrency-primitive implementation, giving impl-parser, impl-protocol, impl-interpreter, impl-jit, impl-datastructure, and impl-concurrency.

The build.rs script gives has-build-exec; cargo has no install-time hook, so has-install-exec. The script reads CARGO_CFG_TARGET_ARCH, CARGO_CFG_TARGET_POINTER_WIDTH, and the CURVE25519_DALEK_BACKEND/CURVE25519_DALEK_BITS overrides, validates them against fixed allow-lists, calls rustc_version, and emits only cargo:rustc-cfg directives selecting the field-arithmetic backend (simd for x86_64 plus 64-bit pointer width, else serial) and limb width. It opens no files, makes no network calls, and spawns no processes, supporting build-exec-safe, build-exec-no-network, build-exec-no-write-out, build-exec-deterministic, and build-exec-minimal.

The crate confines unsafe to the SIMD backend; 37 unsafe sites exist (uses-unsafe). 34 are in src/backend/vector/ (packed_simd.rs 17, ifma/field.rs 9, avx2/field.rs 8): AVX2 and AVX512-IFMA intrinsics plus two core::mem::transmute array-to-SIMD reinterprets in const constructors, each with a // SAFETY: comment. The module header documents the single shared invariant: the code assumes the host CPU supports at least AVX2, an invariant the dispatcher in src/backend/mod.rs upholds by gating each entry point behind a cpufeatures CPUID token (cpuid_avx2, cpuid_avx512ifma) and falling back to the serial backend when absent. The three remaining sites are a 'static reference reinterpret for the precomputed basepoint table at constants.rs:88 and two core::ptr::read_volatile(&local) optimization barriers in the serial scalar code; all read through valid live references. No raw-pointer dereferences or get_unchecked appear. Each site was reviewed and its invariant holds, supporting unsafe-safe, unsafe-documented, and unsafe-minimal.

Testing is inline: 86 #[test] functions across 21 #[cfg(test)] modules covering field, scalar, Edwards, Montgomery, and Ristretto operations, including encode/decode round-trips and known-answer vectors (has-unit-tests; crypto-impl-tested given the known-answer coverage). There are no Rust integration tests (the tests/ directory holds only build_tests.sh), property tests, or fuzz harness in the published crate, giving has-integration-tests, has-property-tests, and has-fuzz-tests.

Conclusion

The audit found no security or safety issues and produced zero findings. curve25519-dalek 4.1.3 implements Curve25519 / ristretto255 group arithmetic with a compile-time-selected backend; build.rs emits cfg directives choosing the serial 32/64-bit path or an x86_64 simd path. The unsafe surface is 37 sites, concentrated in the AVX2/AVX512-IFMA vector backend as intrinsic calls and two const transmutes, with the AVX2-presence invariant enforced at run time via cpufeatures CPUID dispatch; each site was reviewed and its invariant holds. The crate performs no network, filesystem, process, or environment I/O at run time. Field-arithmetic correctness and constant-time / side-channel resistance were scoped out and left unasserted; the crate documents constant-time intent and uses subtle, but this audit does not independently vouch for those properties.

Findings

No findings.

Annotations(8)

Cargo.toml

Edition 2021, MSRV 1.60.0. default = ["alloc", "precomputed-tables", "zeroize"]; none of the default features pull in network, filesystem, or process capability. Mandatory runtime dependencies are cfg-if, cpufeatures (runtime CPUID detection for the SIMD backend), curve25519-dalek-derive, fiat-crypto, and subtle (constant-time selection/comparison primitives). Optional, feature-gated dependencies add trait impls or extra APIs: digest, ff, group, rand_core, serde, zeroize. rustc_version is the sole build dependency, used by build.rs. Justifies uses-network, uses-filesystem, uses-environment, uses-exec, uses-concurrency.

build.rs

build.rs selects the field-arithmetic backend and limb size at compile time. It reads CARGO_CFG_TARGET_ARCH, CARGO_CFG_TARGET_POINTER_WIDTH, and the developer overrides CARGO_CFG_CURVE25519_DALEK_BACKEND / CARGO_CFG_CURVE25519_DALEK_BITS from the environment, validates them against fixed allow-lists (fiat|serial|simd and 32|64, exiting with a cargo:warning on anything else), and emits cargo:rustc-cfg=curve25519_dalek_backend=... and cargo:rustc-cfg=curve25519_dalek_bits=.... The default selection is simd+64 for x86_64, serial+64 for other 64-bit targets, and serial+32 otherwise. It also calls rustc_version::version() to emit a diagnostics cfg for rustc <= 1.64. The script performs no network access, opens no files, spawns no processes, and writes no generated source; its only outputs are cargo: directives derived from environment-provided target metadata and the rustc version. Justifies has-build-exec, build-exec-safe, build-exec-no-network, build-exec-no-write-out, build-exec-deterministic, build-exec-minimal.

src/backend/mod.rs

src/backend/mod.rs, line 51-68

    Serial,
}

#[inline]
fn get_selected_backend() -> BackendKind {
    #[cfg(all(curve25519_dalek_backend = "simd", nightly))]
    {
        cpufeatures::new!(cpuid_avx512, "avx512ifma", "avx512vl");
        let token_avx512: cpuid_avx512::InitToken = cpuid_avx512::init();
        if token_avx512.get() {
            return BackendKind::Avx512;
        }
    }

    #[cfg(curve25519_dalek_backend = "simd")]
    {
        cpufeatures::new!(cpuid_avx2, "avx2");
        let token_avx2: cpuid_avx2::InitToken = cpuid_avx2::init();

Runtime backend dispatch. Each public scalar-multiplication entry point (variable_base_mul, pippenger_optional_multiscalar_mul, mul_base, and the vartime variants) is guarded by a cpufeatures CPUID token: under the simd cfg the function evaluates cpufeatures::new!(cpuid_avx2, "avx2") (or cpuid_avx512ifma for IFMA), and only routes to the backend::vector SIMD path when token.get() is true, otherwise calling backend::serial. This is the mechanism that upholds the AVX2-presence invariant documented in packed_simd.rs, so the SIMD intrinsics are never reached on hardware lacking the feature. Justifies unsafe-safe.

src/backend/serial/u64/scalar.rs

src/backend/serial/u64/scalar.rs, line 182-182

            unsafe { core::ptr::read_volatile(&value) }

core::ptr::read_volatile(&value) reads a local stack value through a valid, live reference. It is used as a compiler optimization barrier so the surrounding masking/selection is not optimized into a data-dependent branch, supporting the constant-time intent of scalar reduction. The pointer is always a reference to an in-scope local, so the volatile read is sound. The matching site in src/backend/serial/u32/scalar.rs:193 is identical. Justifies unsafe-safe.

src/backend/vector/packed_simd.rs

src/backend/vector/packed_simd.rs, line 12-25

use core::ops::{Add, AddAssign, BitAnd, BitAndAssign, BitXor, BitXorAssign, Sub};

use curve25519_dalek_derive::unsafe_target_feature;

macro_rules! impl_shared {
    (
        $ty:ident,
        $lane_ty:ident,
        $add_intrinsic:ident,
        $sub_intrinsic:ident,
        $shl_intrinsic:ident,
        $shr_intrinsic:ident,
        $extract_intrinsic:ident
    ) => {

The SIMD unsafe lives in this vector backend. 34 of the 37 unsafe occurrences are in src/backend/vector/ (packed_simd.rs: 17, ifma/field.rs: 9, avx2/field.rs: 8) and consist of AVX2 / AVX512-IFMA intrinsic calls plus two core::mem::transmute array-to-SIMD reinterprets in const constructors (each carrying a // SAFETY: comment citing the layout guarantee); the backend uses no raw-pointer dereferences (from_raw/as_ptr/ptr::) and no get_unchecked. The vectorized types are repr(transparent) wrappers over __m256i. The three remaining unsafe sites are outside the vector backend: src/constants.rs:88 reinterprets a &RistrettoBasepointTable for the precomputed basepoint static, and src/backend/serial/{u32,u64}/scalar.rs each use one core::ptr::read_volatile(&value) of a valid stack reference as an optimization barrier for constant-time code. All read through valid, live references and uphold their invariants. The module documents the single invariant carried by this unsafe: the code assumes the host CPU supports at least AVX2, "an invariant that must be enforced by the callers of this code." That invariant is upheld at the dispatch layer in src/backend/mod.rs, where each public entry point uses cpufeatures::new!(cpuid_avx2, "avx2") (and cpuid_avx512ifma for the IFMA path) to detect the feature at run time via CPUID and falls back to the serial backend when the token is absent. The crate root additionally sets #![cfg_attr(not(test), forbid(unsafe_code))], confining unsafe to the SIMD backend, and the build/compile_error! gates restrict the simd backend to x86_64. Justifies uses-unsafe, unsafe-safe, unsafe-documented, unsafe-minimal.

src/constants.rs

src/constants.rs, line 88-88

pub static RISTRETTO_BASEPOINT_TABLE: &RistrettoBasepointTable = unsafe {

RISTRETTO_BASEPOINT_TABLE is defined as a reference reinterpret of ED25519_BASEPOINT_TABLE in a const initializer. RistrettoBasepointTable is a single-field newtype wrapper over EdwardsBasepointTable, so the cast is a layout-preserving reference reinterpret of a 'static value; the reference is valid for the program's lifetime. One of the 37 unsafe sites; reviewed and sound. Justifies unsafe-safe.

src/lib.rs

Unit tests are inline in src/ under #[cfg(test)]: 86 #[test] functions across 21 test modules, covering field, scalar, Edwards, Montgomery, and Ristretto operations (including encode/decode round-trips and known-answer vectors). There are no Rust integration tests (tests/ contains only build_tests.sh, a shell script that exercises backend build configurations), no proptest/quickcheck property tests, and no fuzz harness. Justifies has-unit-tests, has-integration-tests, has-property-tests, has-fuzz-tests, crypto-impl-tested.

src/scalar.rs

Scalar implements arithmetic modulo the group order l = 2^252 + 27742317777372353535851937790883648493, the order of the Ristretto/Ed25519 prime-order group. Together with field.rs (arithmetic mod p = 2^255 - 19), edwards.rs (twisted-Edwards group operations), montgomery.rs, and ristretto.rs, the crate implements the elliptic-curve group arithmetic underlying X25519 and Ed25519. The code uses subtle constant-time primitives throughout (44 ConstantTimeEq/ct_eq and 45 ConditionallySelectable/conditional_select sites) and zeroize for clearing secret material, documenting constant-time intent. Justifies uses-crypto, impl-crypto, impl-algorithm.