cargo / typenum / audit
cargo : typenum @ 1.20.0
PE Patrick Elsen signed 2026-05-27 published 2026-05-27

Claims

algorithm-impl-boundsalgorithm-impl-correctalgorithm-impl-safealgorithm-impl-testeddatastructure-impl-boundsdatastructure-impl-correctdatastructure-impl-safedatastructure-impl-testedhas-binarieshas-build-exechas-fuzz-testshas-install-exechas-integration-testshas-property-testshas-unit-testsimpl-algorithmimpl-concurrencyimpl-cryptoimpl-datastructureimpl-interpreterimpl-jitimpl-parserimpl-protocolis-benignuses-concurrencyuses-cryptouses-environmentuses-execuses-filesystemuses-interpreteruses-jituses-networkuses-unsafe

Summary

typenum 1.20.0 is a compile-time type-level number library (UInt/PInt/NInt/TArr with trait-resolution arithmetic). #![forbid(unsafe_code)] + #![no_std]; the build script was removed in 1.18.0 and the generated code is checked in. 1743 generated integration tests. No findings; suitable for security-sensitive use.

Report

Subject

typenum is a Rust library for type-level numbers evaluated at compile time. It provides UInt/UTerm for unsigned integers, PInt/NInt/Z0 for signed integers, B0/B1 for bits, and a TArr/ATerm type-level array of typed numbers. Type operators (Add, Sub, Mul, Div, Rem, Shl, Shr, BitAnd, BitOr, BitXor, Pow, Cmp, Sqrt, Log2, Gcd, ...) are implemented via trait resolution so arithmetic happens during type-checking. The crate is widely depended on by the cryptography and embedded ecosystems (e.g. generic-array, digest, rand).

Methodology

The published crate archive was unpacked and compared against the upstream paholg/typenum repository at the commit recorded in .cargo_vcs_info.json. The src/ and tests/ trees match byte-for-byte; the only differences from VCS are files explicitly listed in the Cargo.toml exclude field (.github/, clippy.toml, flake.lock, flake.nix, justfile, .envrc) and the offline generate/ workspace member used to produce the checked-in generated code. The packaged Cargo.toml differs from Cargo.toml.orig only in cargo's standard normalisation.

Every source file under src/ was read in full. Three of the files (src/gen/consts.rs, src/gen/op.rs, src/gen/generic_const_mappings.rs) are marked // THIS IS GENERATED CODE and total ~12k lines; their producer (vcs-root/generate/src/main.rs) was inspected, and spot-checks of U6, U10, U12 confirm the encoding matches the generator. The 21k-line tests/generated.rs was sampled (1743 test functions) and its generator surveyed in vcs-root/generate/src/tests.rs. No CI run was performed locally; the upstream justfile runs cargo test --features strict plus clippy and doc builds as the maintainer's release gate.

Results

The crate has #![forbid(unsafe_code)] and #![no_std] at the top of src/lib.rs, which is a compiler-enforced ban on any unsafe block, unsafe fn, or unsafe impl anywhere in the codebase. A grep across the source tree confirms no occurrences of Mutex, RwLock, atomic::, Atomic, RefCell, thread_local!, spawn, or any std::{net,fs,env,process} use beyond doc examples. The crate depends only on core::ops, core::cmp, and core::marker. Justifies uses-crypto, uses-exec, uses-jit, uses-interpreter, uses-network, uses-filesystem, uses-environment, uses-concurrency, uses-unsafe, and impl-crypto, impl-parser, impl-interpreter, impl-jit, impl-protocol, impl-concurrency.

The crate ships no binary artefacts (justifying has-binaries), no build.rs (Cargo.toml declares build = false, and CHANGELOG entries for 1.18.0 and 1.20.0 record the build-script removal), and no procedural-macro components (no [lib] proc-macro = true, no #[proc_macro*] attributes). Justifies has-build-exec and has-install-exec.

The library implements a compile-time data structure (UInt/PInt/NInt/TArr) and the arithmetic algorithms over it (justifying impl-datastructure and impl-algorithm). Correctness is established by the 1743 generated integration tests in tests/generated.rs (justifying has-integration-tests; the crate ships no in-source #[cfg(test)] modules, no property-based tests, and no fuzz harness, justifying has-unit-tests, has-property-tests, and has-fuzz-tests as false) covering each operator across small operand combinations, by the doc-test examples in each operator module, and by the type-system itself: a wrong implementation would produce a wrong Output associated type that the tests immediately catch via the Same<expected> assertion pattern. The implementation has no notion of runtime time/space bounds — operations happen at compile time, and the recursion depth is bounded by the bit-width of the operand types, which is fixed per-type. Justifies datastructure-impl-safe, datastructure-impl-correct, datastructure-impl-tested, datastructure-impl-bounds, algorithm-impl-safe, algorithm-impl-correct, algorithm-impl-tested, algorithm-impl-bounds.

No findings were recorded. The package is benign: no malicious behaviour, no obfuscation, no targeted payloads. Justifies is-benign.

Conclusion

typenum is a low-risk, well-disciplined library. The #![forbid(unsafe_code)] declaration eliminates an entire class of memory-safety risk by compiler enforcement, the no_std posture limits the capability surface to pure computation, and the absence of a build script (since 1.18.0) eliminates the compile-time code-execution attack surface that previously existed. The bulk of the source is mechanically generated and exhaustively tested. Suitable for use in security-sensitive contexts.

Findings

No findings.

Annotations(6)

Cargo.toml

Cargo.toml, line 19-30

    "Andre Bogus <bogusandre@gmail.com>",
]
build = false
exclude = [
    "/.github/",
    "/clippy.toml",
    "/flake.lock",
    "/flake.nix",
    "/justfile",
    "/.envrc",
]
autolib = false

build = false explicitly disables build-script compilation. The CHANGELOG records that the previous build.rs was removed in 1.18.0 and the build output was checked in instead; 1.20.0's CHANGELOG entry repeats this. The published archive contains no build.rs. The exclude list filters .github/, flake.*, justfile, and clippy.toml from the package. The crate is a library (no [[bin]]) with no procedural-macro declaration. Justifies has-build-exec and has-install-exec.

src/gen/consts.rs

src/gen/consts.rs, line 1-65

// THIS IS GENERATED CODE
#![allow(missing_docs)]
use crate::int::{NInt, PInt};
/**
Type aliases for many constants.

This file is generated by typenum's build script.

For unsigned integers, the format is `U` followed by the number. We define aliases for

- Numbers 0 through 1024
- Powers of 2 below `u64::MAX`
- Powers of 2 minus 1 below `u64::MAX`
- Powers of 10 below `u64::MAX`

These alias definitions look like this:

```rust
use typenum::{B0, B1, UInt, UTerm};

# #[allow(dead_code)]
type U6 = UInt<UInt<UInt<UTerm, B1>, B1>, B0>;
```

For positive signed integers, the format is `P` followed by the number and for negative
signed integers it is `N` followed by the number. For the signed integer zero, we use
`Z0`. We define aliases for

- Numbers -1024 through 1024
- Powers of 2 between `i64::MIN` and `i64::MAX`
- Powers of 2 minus 1 between `i64::MIN` and `i64::MAX`
- Powers of 10 between `i64::MIN` and `i64::MAX`

These alias definitions look like this:

```rust
use typenum::{B0, B1, UInt, UTerm, PInt, NInt};

# #[allow(dead_code)]
type P6 = PInt<UInt<UInt<UInt<UTerm, B1>, B1>, B0>>;
# #[allow(dead_code)]
type N6 = NInt<UInt<UInt<UInt<UTerm, B1>, B1>, B0>>;
```

# Example
```rust
# #[allow(unused_imports)]
use typenum::{U0, U1, U2, U3, U4, U5, U6};
# #[allow(unused_imports)]
use typenum::{N3, N2, N1, Z0, P1, P2, P3};
# #[allow(unused_imports)]
use typenum::{U774, N17, N10000, P1024, P4096};
```

We also define the aliases `False` and `True` for `B0` and `B1`, respectively.
*/
use crate::uint::{UInt, UTerm};

pub use crate::bit::{B0, B1};
pub use crate::int::Z0;

pub type True = B1;
pub type False = B0;
pub type U0 = UTerm;
pub type U1 = UInt<UTerm, B1>;

Header documents this file as machine-generated by typenum's (now-removed) build script. The 6.5k-line body is exclusively type aliases of the form pub type U6 = UInt<UInt<UInt<UTerm, B1>, B1>, B0>; mapping decimal numerals (and powers of 2, powers of 10, and constants like 3600) to nested UInt/PInt/NInt types. The generator source is at vcs-root/generate/src/main.rs; spot checks of U6, U10, U12 show the encoding matches the documented bit-pattern (little-endian bits with B1 = set).

src/lib.rs

src/lib.rs, line 42-48

#![no_std]
#![forbid(unsafe_code)]
#![warn(missing_docs)]
#![cfg_attr(feature = "strict", deny(missing_docs))]
#![cfg_attr(feature = "strict", deny(warnings))]
#![doc(html_root_url = "https://docs.rs/typenum/1.20.0")]
#![cfg_attr(docsrs, feature(doc_cfg))]

Crate-level attributes: #![no_std] and #![forbid(unsafe_code)]. The forbid is a hard compiler-enforced ban on any unsafe block, unsafe fn, or unsafe impl anywhere in the crate. Combined with the no-std restriction, the crate has no access to threading, networking, filesystem, environment, process spawning, or any other capability beyond libcore. Justifies uses-unsafe, uses-network, uses-filesystem, uses-environment, uses-exec, uses-concurrency, uses-jit, uses-interpreter, uses-crypto.

src/tuple.rs

src/tuple.rs, line 41-129

macro_rules! generate_tuple_impls {
    (
        $( $u:ident ),* ;
        $( $t:ident ),* ;
    ) => {
        generate_tuple_impls!(@__recur__
             [ ] [ $( $u )* ];
             [ ] [ $( $t )* ];
        );
    };

    (@__recur__
        [ ] [ ];
        [ ] [ ];
    ) => { /* terminal-case */ };

    (@__recur__
        [ $( $uhead:ident )* ] [ $u:ident $( $utail:ident )+ ];
        [ $( $thead:ident )* ] [ $t:ident $( $ttail:ident )+ ];
    ) => {
        generate_tuple_impls!(@__recur__
            [ $( $uhead )* $u ] [ $( $utail )* ];
            [ $( $thead )* $t ] [ $( $ttail )* ];
        );

        generate_tuple_impls!(@__expand_at__
            [ $( $uhead )* ] [ $u $( $utail )* ];
            [ $( $thead )* ] [ $t $( $ttail )* ];
        );
    };

    (@__recur__
        [ $( $uhead:ident )* ] [ $u:ident ];
        [ $( $thead:ident )* ] [ $t:ident ];
    ) => {

        impl< $( $thead, )* $t, > $crate::Len   for ( $( $thead, )* $t, ) {
            type Output = <$crate::$u as core::ops::Add<$crate::B1>>::Output;

            #[inline]
            fn len(&self) -> Self::Output {
                Self::Output::new()
            }
        }

        generate_tuple_impls!(@__recur__
            [ ] [ $( $uhead )* ];
            [ ] [ $( $thead )* ];
        );

        generate_tuple_impls!(@__expand_at__
            [ $( $uhead )* ] [ $u ];
            [ $( $thead )* ] [ $t ];
        );
    };


    (@__expand_at__
        [ $( $uhead:ident )* ] [ $u:ident $( $utail:ident )* ];
        [ $( $thead:ident )* ] [ $t:ident $( $ttail:ident )* ];
    ) => {
        impl< $( $thead, )* $t, $( $ttail ),* > core::ops::Index<$crate::$u> for ( $( $thead, )* $t, $( $ttail ),* ) {
            type Output = $t;

            fn index(&self, _index: $crate::$u) -> &Self::Output {
                #[allow(unused_variables)]
                #[allow(non_snake_case)]
                let ( $( $thead, )* $t, ..) = self;

                $t
            }
        }

        impl< $( $thead, )* $t, $( $ttail ),* > core::ops::IndexMut<$crate::$u> for ( $( $thead, )* $t, $( $ttail ),* ) {
            fn index_mut(&mut self, _index: $crate::$u) -> &mut Self::Output {
                #[allow(unused_variables)]
                #[allow(non_snake_case)]
                let ( $( $thead, )* $t, ..) = self;

                $t
            }
        }
    };
}

generate_tuple_impls! {
    U0, U1, U2, U3, U4, U5, U6, U7, U8, U9, U10, U11;
    T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11;
}

generate_tuple_impls! is a single macro_rules definition expanded once with arities U0..U11 to implement core::ops::Index and core::ops::IndexMut for tuples of size 1-12. The implementations destructure the tuple with a positional pattern and return the matching field. No unsafe, no allocation. Compile-time errors on out-of-bounds (no trait impl for the index).

src/uint.rs

src/uint.rs, line 1-30

//! Type-level unsigned integers.
//!
//!
//! **Type operators** implemented:
//!
//! From `::core::ops`: `BitAnd`, `BitOr`, `BitXor`, `Shl`, `Shr`, `Add`, `Sub`,
//!                 `Mul`, `Div`, and `Rem`.
//! From `typenum`: `Same`, `Cmp`, and `Pow`.
//!
//! Rather than directly using the structs defined in this module, it is recommended that
//! you import and use the relevant aliases from the [consts](../consts/index.html) module.
//!
//! # Example
//! ```rust
//! use std::ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Rem, Shl, Shr, Sub};
//! use typenum::{Unsigned, U1, U2, U3, U4};
//!
//! assert_eq!(<U3 as BitAnd<U2>>::Output::to_u32(), 2);
//! assert_eq!(<U3 as BitOr<U4>>::Output::to_u32(), 7);
//! assert_eq!(<U3 as BitXor<U2>>::Output::to_u32(), 1);
//! assert_eq!(<U3 as Shl<U1>>::Output::to_u32(), 6);
//! assert_eq!(<U3 as Shr<U1>>::Output::to_u32(), 1);
//! assert_eq!(<U3 as Add<U2>>::Output::to_u32(), 5);
//! assert_eq!(<U3 as Sub<U2>>::Output::to_u32(), 1);
//! assert_eq!(<U3 as Mul<U2>>::Output::to_u32(), 6);
//! assert_eq!(<U3 as Div<U2>>::Output::to_u32(), 1);
//! assert_eq!(<U3 as Rem<U2>>::Output::to_u32(), 1);
//! ```

use crate::{

UInt/UTerm and the trait impls in this 2871-line module implement a compile-time peano/binary representation of unsigned integers plus its arithmetic (Add, Sub, Mul, Div, Rem, Shl, Shr, BitAnd, BitOr, BitXor, Pow, Cmp, Sqrt, Log2, Gcd). Operations are evaluated by trait resolution at compile time; the runtime fn to_u32() etc. just read out the embedded const values. Justifies impl-datastructure and impl-algorithm.

tests/generated.rs

tests/generated.rs, line 1-100

// THIS IS GENERATED CODE
use core::cmp::Ordering;
use core::ops::*;
use typenum::*;

#[test]
#[allow(non_snake_case)]
fn test_0_BitAnd_0() {
    type A = UTerm;
    type B = UTerm;
    type U0 = UTerm;

    #[allow(non_camel_case_types)]
    type U0BitAndU0 = <<A as BitAnd<B>>::Output as Same<U0>>::Output;

    assert_eq!(
        <U0BitAndU0 as Unsigned>::to_u64(),
        <U0 as Unsigned>::to_u64()
    );
}
#[test]
#[allow(non_snake_case)]
fn test_0_BitOr_0() {
    type A = UTerm;
    type B = UTerm;
    type U0 = UTerm;

    #[allow(non_camel_case_types)]
    type U0BitOrU0 = <<A as BitOr<B>>::Output as Same<U0>>::Output;

    assert_eq!(
        <U0BitOrU0 as Unsigned>::to_u64(),
        <U0 as Unsigned>::to_u64()
    );
}
#[test]
#[allow(non_snake_case)]
fn test_0_BitXor_0() {
    type A = UTerm;
    type B = UTerm;
    type U0 = UTerm;

    #[allow(non_camel_case_types)]
    type U0BitXorU0 = <<A as BitXor<B>>::Output as Same<U0>>::Output;

    assert_eq!(
        <U0BitXorU0 as Unsigned>::to_u64(),
        <U0 as Unsigned>::to_u64()
    );
}
#[test]
#[allow(non_snake_case)]
fn test_0_Shl_0() {
    type A = UTerm;
    type B = UTerm;
    type U0 = UTerm;

    #[allow(non_camel_case_types)]
    type U0ShlU0 = <<A as Shl<B>>::Output as Same<U0>>::Output;

    assert_eq!(<U0ShlU0 as Unsigned>::to_u64(), <U0 as Unsigned>::to_u64());
}
#[test]
#[allow(non_snake_case)]
fn test_0_Shr_0() {
    type A = UTerm;
    type B = UTerm;
    type U0 = UTerm;

    #[allow(non_camel_case_types)]
    type U0ShrU0 = <<A as Shr<B>>::Output as Same<U0>>::Output;

    assert_eq!(<U0ShrU0 as Unsigned>::to_u64(), <U0 as Unsigned>::to_u64());
}
#[test]
#[allow(non_snake_case)]
fn test_0_Add_0() {
    type A = UTerm;
    type B = UTerm;
    type U0 = UTerm;

    #[allow(non_camel_case_types)]
    type U0AddU0 = <<A as Add<B>>::Output as Same<U0>>::Output;

    assert_eq!(<U0AddU0 as Unsigned>::to_u64(), <U0 as Unsigned>::to_u64());
}
#[test]
#[allow(non_snake_case)]
fn test_0_Mul_0() {
    type A = UTerm;
    type B = UTerm;
    type U0 = UTerm;

    #[allow(non_camel_case_types)]
    type U0MulU0 = <<A as Mul<B>>::Output as Same<U0>>::Output;

    assert_eq!(<U0MulU0 as Unsigned>::to_u64(), <U0 as Unsigned>::to_u64());
}
#[test]
#[allow(non_snake_case)]

1743 generated #[test] functions, one per (operator, lhs, rhs) tuple over small integer values, checking that the type-level result matches the value-level expectation. Header marks the file as generated. The generator lives at vcs-root/generate/src/tests.rs. This is the principal correctness harness for the crate's type-level arithmetic. Justifies algorithm-impl-tested and algorithm-impl-correct.