multi-hash 2.0.0

Multihash self-describing cryptographic hash data
docs.rs failed to build multi-hash-2.0.0
Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.

Build Status License Crates.io Documentation

multi-hash

Rust implementation of the Multihash specification for self-describing cryptographic hash digests.

Multihash is a self-describing format. It pairs a hash algorithm identifier (a multicodec tag) with the raw digest bytes. This lets systems switch hash algorithms without a break in compatibility. The crate gives 25 supported hash algorithms, type-safe wrappers, serde integration, and multibase encoding via the multi-util crate stack. The codec set includes three extendable-output functions (XOF): Blake3, Shake128, and Shake256. You choose the digest length of an XOF with the builder, from 1 to MAX_HASH_LENGTH bytes (16 MiB).

Table of Contents

Features

  • 25 hash algorithms. SHA1, SHA2 family, SHA3 family, Blake2, Blake3, MD5, RIPEMD. Blake3, Shake128, and Shake256 are extendable-output functions (XOF) with a caller-chosen digest length.
  • Builder pattern. A fluent API to create multihashes from raw data or existing digests.
  • Multibase encoding. The EncodedMultihash smart pointer gives a base-encoded string representation via BaseEncoded from multi-util.
  • Serde support. JSON gives the codec name string. Binary gives the varint bytes. The serde feature gates it.
  • Binary round-trip. Into<Vec<u8>> and TryFrom<&[u8]> for the raw wire format.
  • Type-safe newtypes. HashDigest and AlgorithmId wrappers.
  • Zero unsafe code. #![deny(unsafe_code)] is set at the crate root.
  • Thread-safe. All types are Send + Sync.

Install

Add this to your Cargo.toml:

[dependencies]
multi-hash = "2.0"

To disable serde support:

[dependencies]
multi-hash = { version = "2.0", default-features = false }

MSRV: Rust 1.99 (Edition 2024).

Supported Algorithms

Secure algorithms (recommended for cryptographic use)

Algorithm Codec Digest Size
Blake2b-256 Blake2B256 32 bytes
Blake2b-384 Blake2B384 48 bytes
Blake2b-512 Blake2B512 64 bytes
Blake2s-256 Blake2S256 32 bytes
Blake3 Blake3 Caller-chosen, 1 to MAX_HASH_LENGTH bytes (recommended 32)
SHA3-256 Sha3256 32 bytes
SHA3-384 Sha3384 48 bytes
SHA3-512 Sha3512 64 bytes
Shake128 Shake128 Caller-chosen, 1 to MAX_HASH_LENGTH bytes (recommended 32)
Shake256 Shake256 Caller-chosen, 1 to MAX_HASH_LENGTH bytes (recommended 64)

See SAFE_HASH_CODECS for the constant array.

Legacy algorithms (for compatibility)

Algorithm Codec Digest Size
SHA1 Sha1 20 bytes
SHA2-224 Sha2224 28 bytes
SHA2-256 Sha2256 32 bytes
SHA2-384 Sha2384 48 bytes
SHA2-512 Sha2512 64 bytes
SHA2-512/224 Sha2512224 28 bytes
SHA2-512/256 Sha2512256 32 bytes
Blake2b-224 Blake2B224 28 bytes
Blake2s-224 Blake2S224 28 bytes
MD5 Md5 16 bytes
RIPEMD-128 Ripemd128 16 bytes
RIPEMD-160 Ripemd160 20 bytes
RIPEMD-256 Ripemd256 32 bytes
RIPEMD-320 Ripemd320 40 bytes
SHA3-224 Sha3224 28 bytes

See HASH_CODECS for the constant array of all 25 supported codecs.

The extendable-output functions (Blake3, Shake128, and Shake256) accept a caller-chosen digest length from 1 to MAX_HASH_LENGTH bytes (16 MiB), which you set with Builder::output_len().

Usage

Computing a Hash

use multi_hash::Builder;
use multi_codec::Codec;

// Compute a SHA2-256 hash over data fed in chunks
let mut builder = Builder::new(Codec::Sha2256).unwrap();
builder.update(b"hello world");
let multihash = builder.try_build().unwrap();

assert_eq!(multihash.codec(), Codec::Sha2256);
assert_eq!(multihash.as_ref().len(), 32); // SHA2-256 outputs 32 bytes

Building from an Existing Digest

If you already have a hash digest, for example from an external hashing library:

use multi_hash::Builder;
use multi_codec::Codec;

let digest = vec![0u8; 32]; // pre-computed SHA2-256 digest
let multihash = Builder::new(Codec::Sha2256)
    .unwrap()
    .with_hash(digest)
    .try_build()
    .unwrap();

Extendable-Output Functions

Blake3, Shake128, and Shake256 are extendable-output functions (XOF). They produce a digest of any length from 1 to MAX_HASH_LENGTH bytes. Set the length with output_len(). A streamed build of an XOF codec fails with Error::OutputLenRequired without a length:

use multi_hash::{Builder, Error};
use multi_codec::Codec;

// Stream data through SHAKE128 and squeeze 64 digest bytes
let mut shake = Builder::new(Codec::Shake128).unwrap();
shake.update(b"hello world");
shake.output_len(64);
let shake_digest = shake.try_build().unwrap();
assert_eq!(shake_digest.as_ref().len(), 64);

// BLAKE3 uses the same length policy. A 32-byte digest equals the classic BLAKE3 digest.
let mut blake = Builder::new(Codec::Blake3).unwrap();
blake.update(b"hello world");
blake.output_len(32);
let blake_digest = blake.try_build().unwrap();
assert_eq!(blake_digest.as_ref().len(), 32);

// A streamed build of an XOF codec fails without an output length
let mut builder = Builder::new(Codec::Shake256).unwrap();
builder.update(b"hello world");
match builder.try_build() {
    Err(Error::OutputLenRequired { codec }) => {
        eprintln!("Set output_len() for {:?} with a length from 1 to MAX_HASH_LENGTH", codec);
    }
    Err(e) => eprintln!("Other error: {}", e),
    Ok(_) => unreachable!(),
}

You can also wrap an existing XOF digest. with_hash() accepts any digest length from 1 to MAX_HASH_LENGTH bytes for an XOF codec. Notes on XOF digests:

  • Prefix consistency. For the same input, the first N bytes of a long digest equal the shorter digest. Two different lengths still encode as distinct multihashes, because the encoded length prefix differs.
  • No extension of a built digest. A built Multihash digest cannot extend to a longer digest. A different length needs a rehash through a fresh builder.
  • Different algorithms. A 32-byte Shake256 digest is not a Sha3256 digest. A 32-byte Blake3 digest is not a BLAKE2b-256 digest. The codecs name different algorithms.
  • Length and security. The sponge capacity fixes the security strength of the XOF. The chosen output length bounds collision resistance. Use at least 32 bytes for Blake3 and Shake128, and at least 64 bytes for Shake256. Longer BLAKE3 outputs give no additional security.

Encoding and Decoding

Multihashes encode as codec || length || hash (varint-prefixed):

use multi_hash::{Builder, Multihash};
use multi_codec::Codec;

let mut builder = Builder::new(Codec::Sha2256).unwrap();
builder.update(b"data");
let mh1 = builder.try_build().unwrap();

// Encode to binary (varint wire format)
let bytes: Vec<u8> = mh1.clone().into();

// Decode from binary
let mh2 = Multihash::try_from(bytes.as_ref()).unwrap();
assert_eq!(mh1, mh2);

Base Encoding

Use try_build_encoded() with a specific base. It gives an EncodedMultihash that supports Display and TryFrom<&str>:

use multi_hash::Builder;
use multi_codec::Codec;
use multi_base::Base;

let mut builder = Builder::new(Codec::Sha2256).unwrap();
builder.update(b"data");
let encoded = builder
    .with_base_encoding(Base::Base58Btc)
    .try_build_encoded()
    .unwrap();

// Display as a base58-encoded multihash string
let base58_string = encoded.to_string();
println!("Multihash: {}", base58_string);

// Parse back from string
use multi_hash::EncodedMultihash;
let decoded: EncodedMultihash = EncodedMultihash::try_from(base58_string.as_str()).unwrap();
assert_eq!(encoded, decoded);

Converting to EncodedMultihash

You can convert a Multihash to an EncodedMultihash with .into(). The default base is Base16Lower. You can also use EncodedMultihash::new() with a base of your choice:

use multi_hash::{Builder, EncodedMultihash};
use multi_base::Base;
use multi_codec::Codec;

let mut builder = Builder::new(Codec::Sha3384).unwrap();
builder.update(b"for great justice, move every zig!");
let mh = builder.try_build().unwrap();

// Uses the preferred encoding for multihash objects: Base16Lower
let encoded_mh1: EncodedMultihash = mh.clone().into();

// Or choose a specific base encoding
let encoded_mh2: EncodedMultihash = EncodedMultihash::new(Base::Base32Upper, mh);

Serde Integration

With the serde feature on by default, Multihash implements Serialize and Deserialize. Human-readable formats give the codec name and the hex digest. Binary formats give the varint bytes:

use multi_hash::Builder;
use multi_codec::Codec;
use serde::{Serialize, Deserialize};

#[derive(Serialize, Deserialize, Debug, PartialEq)]
struct DocumentHash {
    hash: multi_hash::Multihash,
    timestamp: u64,
}

let mut builder = Builder::new(Codec::Sha2256).unwrap();
builder.update(b"document content");
let doc = DocumentHash {
    hash: builder.try_build().unwrap(),
    timestamp: 1234567890,
};

// Serialize to JSON (human-readable - codec name + hex digest)
let json = serde_json::to_string(&doc).unwrap();
println!("{}", json);

// Deserialize from JSON
let deserialized: DocumentHash = serde_json::from_str(&json).unwrap();
assert_eq!(doc, deserialized);

Error Handling

All conversion and builder errors return Result with a structured Error enum. Builder::new() is fallible and returns Error::UnsupportedHash for an unknown codec:

use multi_hash::{Builder, Error};
use multi_codec::Codec;

// Handle unsupported algorithms
match Builder::new(Codec::Identity) {
    Err(Error::UnsupportedHash { codec }) => {
        eprintln!("Algorithm {:?} not supported", codec);
    }
    Err(e) => eprintln!("Other error: {}", e),
    Ok(_) => unreachable!(),
}

// Handle missing hash data
match Builder::new(Codec::Sha2256).unwrap().try_build() {
    Err(Error::MissingHash) => {
        eprintln!("Must call with_hash() or update() before try_build()");
    }
    Err(e) => eprintln!("Other error: {}", e),
    Ok(_) => unreachable!(),
}

A streamed build of an XOF codec needs an output length:

use multi_hash::{Builder, Error};
use multi_codec::Codec;

// Handle a streamed build of an XOF codec without an output length
let mut builder = Builder::new(Codec::Shake128).unwrap();
builder.update(b"some data");
match builder.try_build() {
    Err(Error::OutputLenRequired { codec }) => {
        eprintln!("Set output_len() from 1 to MAX_HASH_LENGTH for {:?}", codec);
    }
    Err(e) => eprintln!("Other error: {}", e),
    Ok(_) => unreachable!(),
}

// Handle an XOF output length outside the 1..=MAX_HASH_LENGTH policy
let mut builder = Builder::new(Codec::Shake256).unwrap();
builder.update(b"some data");
builder.output_len(0);
match builder.try_build() {
    Err(Error::OutputLenInvalid { codec, output_len, max }) => {
        eprintln!(
            "Algorithm {:?} rejects a length of {}. Use a length from 1 to {}",
            codec, output_len, max
        );
    }
    Err(e) => eprintln!("Other error: {}", e),
    Ok(_) => unreachable!(),
}

The length check runs before any allocation or squeeze.

Type-Safe Newtypes

For more type safety, use the newtype wrappers:

use multi_hash::types::{HashDigest, AlgorithmId};
use multi_codec::Codec;

// Type-safe hash digest
let digest = HashDigest::new(vec![0u8; 32]);
assert_eq!(digest.len(), 32);
assert_eq!(digest.as_bytes().len(), 32);

// Type-safe algorithm identifier
let algo = AlgorithmId::new(Codec::Sha2256);
assert_eq!(algo.codec(), Codec::Sha2256);
assert_eq!(algo.name(), "sha2-256");
assert_eq!(algo.code(), 0x12);

Testing

The crate has 171 tests: 130 test functions and 41 doc examples. The suites cover unit, integration, property-based, security, XOF, FIPS, and doc-test paths:

# Run all tests
cargo test --all-features

# Run specific test suites
cargo test --test edge_case_tests
cargo test --test integration_tests
cargo test --test proptest_tests
cargo test --test security_tests
cargo test --test xof_tests

# Run the FIPS tests, which need the `fips` feature
cargo test --test fips_tests --features fips

# Run benchmarks
cargo bench

Linting and formatting:

cargo fmt --check
cargo clippy --all-targets --all-features -- -D warnings

CI collects coverage with cargo-llvm-cov and uploads the result to Codecov.

Feature Flags

  • serde (default). Enables serde serialization and deserialization. When on, Multihash implements Serialize and Deserialize. Human-readable formats give the codec name and the hex digest. Binary formats give the varint bytes.
  • fips (additive, off by default). Exports FIPS_CODECS (13 codecs) and SAFE_FIPS_CODECS (8 codecs). They list the NIST FIPS approved hash algorithms. SAFE_FIPS_CODECS omits SHA-1 and the 224-bit algorithms that SP 800-131A restricts. Blake2, Blake3, Md5, and the RIPEMD codecs are not FIPS approved. SHA-1 is verification-only under NIST SP 800-131A Rev. 2. The SP 800-131A Rev. 3 draft deprecates SHA-1 and the 224-bit hashes through 2030 and disallows them after 2030.

The fips feature composes with the default serde feature:

[dependencies]
multi-hash = { version = "2.0", features = ["fips"] }

Disabling Default Features

[dependencies]
multi-hash = { version = "2.0", default-features = false }

Security

  • #![deny(unsafe_code)] is set at the crate root.
  • All errors return Result. No path panics on invalid input.
  • All types are Send + Sync with no shared mutable state.
  • Hash computation uses vetted cryptographic libraries from the RustCrypto ecosystem.
  • impl subtle::ConstantTimeEq for Multihash is available for timing-sensitive comparisons.
  • The Varbytes decode path enforces a decoded-size cap (16 MiB) and buffer-length checks. This mitigates CWE-400 and CWE-125.
  • The builder enforces the same 16 MiB cap through MAX_HASH_LENGTH. It checks an XOF output length against 1..=MAX_HASH_LENGTH before it allocates or squeezes. The builder validates the digest length at try_build() time.

See SECURITY.md for the full security policy.

Maintainers

This repo: @dgrantham.

Contribute

Contributions are welcome. Please check out the issues.

Development Guidelines

  • Run cargo fmt before you commit.
  • Run cargo clippy -- -D warnings to check for issues.
  • Add tests for new features.
  • Update documentation for API changes.
  • Run the full test suite: cargo test --all-features.

License

Apache-2.0 (c) Cryptid Technologies