tc_serpent 0.1.0

Serpent and Tnepres block ciphers with 32- to 256-bit keys and a 128-bit block, constant time.
Documentation

tc_serpent

crates.io docs.rs CI license rustc

The Serpent block cipher, an AES finalist, and Tnepres, its byte-swapped variant, with a key of 4 to 32 bytes in four-byte steps and a 128-bit block. The engines implement the tc_block_cipher traits.

The crate is no_std, needs no allocator and contains no unsafe code. It depends on tc_block_cipher and tc_zeroize.

Requires Rust 1.85 or later (edition 2024).

Types

  • SerpentEngine — Serpent in its conventional little-endian representation; constant time.
  • TnepresEngine — Tnepres, which reverses the bytes and words of the key and block; constant time.

Tnepres came out of an endianness issue in the test vectors of the AES submission. It is a different cipher on the wire, not an alias for Serpent, and is here for the data that uses it.

Every engine takes its key through any KeyParams, such as KeyRef. Another key length returns InitError::InvalidKeyLength and keeps the previous key and direction. process_block transforms the first block and returns its length, returning BlockError::NotInitialised before init and BlockError::BufferTooShort for a buffer shorter than a block, without touching the output. Display writes "Serpent" or "Tnepres" without inspecting key material.

The constants BLOCK_BYTES, MIN_KEY_BYTES, MAX_KEY_BYTES and KEY_STEP_BYTES hold the block length, 16, the shortest and longest keys, 4 and 32, and the step between accepted key lengths, 4. SERPENT_ALGO_NAME and TNEPRES_ALGO_NAME hold the names. Every engine implements Default and has const fn new.

Usage

[dependencies]
tc_serpent = "0.1.0"
tc_block_cipher = "0.1.0"
use tc_block_cipher::{BlockCipher, BlockCipherInit, CipherDirection, KeyRef};
use tc_serpent::SerpentEngine;

let key = [0x42; 32];
let mut engine = SerpentEngine::new();
engine
    .init(CipherDirection::Encrypt, &KeyRef::new(&key))
    .expect("Serpent accepts a 32-byte key");
let mut ciphertext = [0; 16];
assert_eq!(engine.process_block(&[0x11; 16], &mut ciphertext), Ok(16));

The crate and engine documentation carry executable examples.

Security

Both engines are constant time with respect to key and block contents: the S-boxes are bitsliced, with Boolean operations and fixed rotations and without secret-dependent branches or table indices. Key length, representation and direction are public.

Each engine keeps its expanded working key rather than borrowing the caller's key, and wipes it when it is replaced and on drop. Wiping does not reach the caller's key buffer or copies left in registers and on the stack.

This is a block-cipher primitive, not a message-encryption format. It supplies no padding, mode of operation or authentication.

Validation

The engines are tested against the vectors of Bouncy Castle's SerpentTest and TnepresTest, in both directions and through their hundred-iteration vectors. Serpent is also tested against RustCrypto's serpent on the known vector and 64 key and block pairs at each key length from 16 bytes, since that crate rejects shorter keys. Contract tests cover error state, untouched output on errors and preserved output tails for each engine. A test requires every engine API to document whether it is constant or variable time.

Missing public documentation and unsafe code are rejected by crate-level lints.

Run these commands from the workspace root:

cargo test -p tc_serpent --locked
cargo clippy -p tc_serpent --all-targets --locked -- -D warnings
cargo fmt -p tc_serpent --check
cargo doc -p tc_serpent --no-deps --locked

Before a release, check the archive contents and run publication validation from a committed checkout:

cargo package -p tc_serpent --list --locked
cargo publish -p tc_serpent --dry-run --locked

The archive includes both license texts, this README, the changelog, the source and the integration tests. It must not include target/ or other build artifacts.

License

Licensed under either the MIT license or the Apache License, Version 2.0, at your option.