Rust implementation of the Multihash specification for self-describing cryptographic hash digests.
Multihash is a self-describing format that pairs a hash algorithm identifier
(multicodec tag) with the raw digest bytes, enabling systems to switch hash
algorithms without breaking compatibility. This crate provides 23 supported hash
algorithms, type-safe wrappers, serde integration, and multibase encoding via
the multi-util crate stack.
- Features
- Install
- Supported Algorithms
- Usage
- Testing
- Feature Flags
- Security
- Maintainers
- Contribute
- License
- 23 Hash Algorithms: SHA1, SHA2 family, SHA3 family, Blake2, Blake3, MD5, RIPEMD
- Builder Pattern: Fluent API for creating multihashes from raw data or existing digests
- Multibase Encoding:
EncodedMultihashsmart pointer for base-encoded string representation viamulti-util'sBaseEncoded - Serde Support: JSON (human-readable → codec name string) and binary (varint bytes) serialization (feature-gated)
- Binary Round-Trip:
Into<Vec<u8>>andTryFrom<&[u8]>for raw wire format - Type-Safe Newtypes:
HashDigestandAlgorithmIdwrappers - Zero Unsafe Code:
#![deny(unsafe_code)]enforced at compile time - Thread-Safe: All types are
Send + Sync
Add this to your Cargo.toml:
[dependencies]
multi-hash = "1.0"To disable serde support:
[dependencies]
multi-hash = { version = "1.0", default-features = false }MSRV: Rust 1.85 (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 |
32 bytes |
| SHA3-256 | Sha3256 |
32 bytes |
| SHA3-384 | Sha3384 |
48 bytes |
| SHA3-512 | Sha3512 |
64 bytes |
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 23 supported codecs.
use multi_hash::Builder;
use multi_codec::Codec;
// Compute a SHA2-256 hash
let multihash = Builder::new_from_bytes(Codec::Sha2256, b"hello world")
.unwrap()
.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 (e.g. 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)
.with_hash(digest)
.try_build()
.unwrap();Multihashes encode as codec || length || hash (varint-prefixed):
use multi_hash::{Builder, Multihash};
use multi_codec::Codec;
let mh1 = Builder::new_from_bytes(Codec::Sha2256, b"data")
.unwrap()
.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 to get an EncodedMultihash that
supports Display and TryFrom<&str>:
use multi_hash::Builder;
use multi_codec::Codec;
use multi_base::Base;
let encoded = Builder::new_from_bytes(Codec::Sha2256, b"data")
.unwrap()
.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);Existing Multihash objects can be converted to EncodedMultihash using .into()
(defaults to Base16Lower) or EncodedMultihash::new() with a chosen base:
use multi_hash::{Builder, EncodedMultihash};
use multi_base::Base;
use multi_codec::Codec;
let mh = Builder::new_from_bytes(Codec::Sha3384, b"for great justice, move every zig!")
.unwrap()
.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 (enabled by default), Multihash implements
Serialize and Deserialize — strings in human-readable formats, varint bytes
in binary formats:
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 doc = DocumentHash {
hash: Builder::new_from_bytes(Codec::Sha2256, b"document content")
.unwrap()
.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:
use multi_hash::{Builder, Error};
use multi_codec::Codec;
// Handle unsupported algorithms
match Builder::new_from_bytes(Codec::Identity, b"data") {
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).try_build() {
Err(Error::MissingHash) => {
eprintln!("Must call with_hash() before build()");
}
Err(e) => eprintln!("Other error: {}", e),
Ok(_) => unreachable!(),
}For additional 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 110 tests across unit, integration, property-based, security, and doc-test suites:
# 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
# Run benchmarks
cargo benchLinting and formatting:
cargo fmt --check
cargo clippy --all-targets --all-features -- -D warningsserde(default): Enables serde serialization/deserialization. When enabled,MultihashimplementsSerializeandDeserialize— codec name and hex digest in human-readable formats, varint bytes in binary formats.
[dependencies]
multi-hash = { version = "1.0", default-features = false }#![deny(unsafe_code)]enforced at compile time- All errors return
Resulttypes — no panics on invalid input - All types are
Send + Syncwith no shared mutable state - Hash computation uses vetted cryptographic libraries from the RustCrypto ecosystem
This repo: @dgrantham.
Contributions welcome! Please check out the issues.
- Run
cargo fmtbefore committing - 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 © Cryptid Technologies