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).
- Features
- Install
- Supported Algorithms
- Usage
- Testing
- Feature Flags
- Security
- Maintainers
- Contribute
- License
- 25 hash algorithms. SHA1, SHA2 family, SHA3 family, Blake2, Blake3, MD5, RIPEMD.
Blake3,Shake128, andShake256are 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
EncodedMultihashsmart pointer gives a base-encoded string representation viaBaseEncodedfrommulti-util. - Serde support. JSON gives the codec name string. Binary gives the varint bytes. The
serdefeature gates it. - Binary round-trip.
Into<Vec<u8>>andTryFrom<&[u8]>for the raw wire format. - Type-safe newtypes.
HashDigestandAlgorithmIdwrappers. - Zero unsafe code.
#![deny(unsafe_code)]is set at the crate root. - Thread-safe. All types are
Send + Sync.
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).
| 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.
| 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().
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 bytesIf 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();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
Multihashdigest cannot extend to a longer digest. A different length needs a rehash through a fresh builder. - Different algorithms. A 32-byte
Shake256digest is not aSha3256digest. A 32-byteBlake3digest 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
Blake3andShake128, and at least 64 bytes forShake256. Longer BLAKE3 outputs give no additional security.
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);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);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);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);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.
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);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 benchLinting and formatting:
cargo fmt --check
cargo clippy --all-targets --all-features -- -D warningsCI collects coverage with cargo-llvm-cov and uploads the result to Codecov.
serde(default). Enables serde serialization and deserialization. When on,MultihashimplementsSerializeandDeserialize. Human-readable formats give the codec name and the hex digest. Binary formats give the varint bytes.fips(additive, off by default). ExportsFIPS_CODECS(13 codecs) andSAFE_FIPS_CODECS(8 codecs). They list the NIST FIPS approved hash algorithms.SAFE_FIPS_CODECSomits 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"] }[dependencies]
multi-hash = { version = "2.0", default-features = false }#![deny(unsafe_code)]is set at the crate root.- All errors return
Result. No path panics on invalid input. - All types are
Send + Syncwith no shared mutable state. - Hash computation uses vetted cryptographic libraries from the RustCrypto ecosystem.
impl subtle::ConstantTimeEq for Multihashis available for timing-sensitive comparisons.- The
Varbytesdecode 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 against1..=MAX_HASH_LENGTHbefore it allocates or squeezes. The builder validates the digest length attry_build()time.
See SECURITY.md for the full security policy.
This repo: @dgrantham.
Contributions are welcome. Please check out the issues.
- Run
cargo fmtbefore you commit. - Run
cargo clippy -- -D warningsto check for issues. - Add tests for new features.
- Update documentation for API changes.
- Run the full test suite:
cargo test --all-features.
Apache-2.0 (c) Cryptid Technologies