Rust implementation of the multicodec specification. The crate provides self-describing protocol and encoding identifiers.
Multicodec is a self-describing multiformat. It identifies protocols, encodings, cryptographic algorithms, and other systems. Each codec has a unique numeric identifier (code), a human-readable name, and a canonical string representation.
This crate provides the Codec enum with 570+ variants. The variants represent all standardized multicodec identifiers from the official specification. A build script generates the enum and its conversions from table.csv. The crate keeps table.csv in sync with the official multicodec table.
- Features
- Install
- Usage
- CLI Example
- Testing
- Updating the Codec Table
- Feature Flags
- Security
- Maintainers
- Contribute
- License
- 570+ codec variants. All standardized multicodec identifiers.
- Type-safe conversions.
TryFromandIntofor all numeric types and strings. - Serde support. JSON and binary serialization. The
serdefeature gates it. no_stdsupport. The crate works inno_stdenvironments withalloc.- Zero unsafe code.
#![deny(unsafe_code)]is set at the crate root. - Thread-safe. All types are
Send + Sync. - Type-safe newtypes.
CodecCodeandCodecNamewrappers. - Varint encoding. The crate encodes codecs as unsigned varints via
multi-trait.
Add this to your Cargo.toml:
[dependencies]
multi-codec = "1.1"For no_std environments, disable std and serde:
[dependencies]
multi-codec = { version = "1.1", default-features = false }To use serde under no_std, enable only the serde feature:
[dependencies]
multi-codec = { version = "1.1", default-features = false, features = ["serde"] }MSRV: Rust 1.85 (Edition 2024).
use multi_codec::Codec;
// Create codec from name
let codec = Codec::try_from("ed25519-pub")?;
assert_eq!(codec, Codec::Ed25519Pub);
// Get codec properties
assert_eq!(codec.code(), 0xED);
assert_eq!(codec.as_str(), "ed25519-pub");
assert_eq!(format!("{:?}", codec), "ed25519-pub (0xed)");
# Ok::<(), multi_codec::Error>(())The crate encodes codecs as unsigned varints via the multi-trait crate:
use multi_codec::Codec;
use multi_trait::TryDecodeFrom;
let codec = Codec::Sha2256;
// Encode to varint bytes
let bytes: Vec<u8> = codec.into();
assert_eq!(bytes, vec![0x12]); // Sha2-256 encodes as a single byte
// Decode from bytes (returns remaining slice for streaming)
let (decoded, remaining) = Codec::try_decode_from(&bytes)?;
assert_eq!(decoded, codec);
assert!(remaining.is_empty());
# Ok::<(), multi_codec::Error>(())use multi_codec::Codec;
// From numeric code
let codec = Codec::try_from(0x12u64)?;
assert_eq!(codec, Codec::Sha2256);
// From string name
let codec = Codec::try_from("sha2-256")?;
assert_eq!(codec, Codec::Sha2256);
// Get code and name
assert_eq!(codec.code(), 0x12);
assert_eq!(codec.as_str(), "sha2-256");
# Ok::<(), multi_codec::Error>(())Signed integer conversions reject negative values:
use multi_codec::{Codec, Error};
match Codec::try_from(-1i64) {
Err(Error::NegativeValue { value }) => eprintln!("Negative not allowed: {}", value),
_ => unreachable!(),
}With the serde feature on by default, Codec serializes as a string in human-readable formats (JSON, TOML). It serializes as varint bytes in binary formats (CBOR, bincode):
use multi_codec::Codec;
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize, Debug, PartialEq)]
struct SignatureInfo {
algorithm: Codec,
public_key: Vec<u8>,
}
let info = SignatureInfo {
algorithm: Codec::Ed25519Pub,
public_key: vec![1, 2, 3, 4],
};
// Serialize to JSON (human-readable - codec name string)
let json = serde_json::to_string(&info)?;
assert!(json.contains("ed25519-pub"));
// Deserialize from JSON
let deserialized: SignatureInfo = serde_json::from_str(&json)?;
assert_eq!(deserialized, info);
# Ok::<(), Box<dyn std::error::Error>>(())All conversion errors return Result with a structured Error enum:
use multi_codec::{Codec, Error};
// Handle invalid names
match Codec::try_from("unknown-codec") {
Err(Error::InvalidName { name }) => {
eprintln!("Unknown codec name: {}", name);
}
Err(e) => eprintln!("Other error: {}", e),
Ok(_) => unreachable!(),
}
// Handle invalid codes
match Codec::try_from(0xDEADBEEFu64) {
Err(Error::InvalidValue { code }) => {
eprintln!("Unknown codec value: 0x{:x}", code);
}
Err(e) => eprintln!("Other error: {}", e),
Ok(_) => unreachable!(),
}For additional type safety, use the newtype wrappers:
use multi_codec::{CodecCode, CodecName};
// Type-safe code value
let code = CodecCode::new(0xED);
assert_eq!(code.get(), 0xED);
assert_eq!(code.to_string(), "0xed");
// Type-safe name
let name = CodecName::new("sha2-256");
assert_eq!(name.as_str(), "sha2-256");
assert!(!name.is_identity());You can encode multiple codecs into one buffer and decode them back in order:
use multi_codec::Codec;
use multi_trait::{TryDecodeFrom, EncodeIntoBuffer};
let codecs = vec![Codec::Identity, Codec::Sha2256, Codec::Ed25519Pub];
// Encode all codecs into one buffer
let mut buffer = Vec::new();
for codec in &codecs {
let code: u64 = (*codec).into();
code.encode_into_buffer(&mut buffer);
}
// Decode them back sequentially
let mut remaining = buffer.as_slice();
for expected in &codecs {
let (code, rest) = u64::try_decode_from(remaining)?;
let decoded = Codec::try_from(code)?;
assert_eq!(decoded, *expected);
remaining = rest;
}
# Ok::<(), Box<dyn std::error::Error>>(())The crate includes a varuint encode/decode example in examples/uvi.rs:
# Encode a hex number as varuint
cargo run --example uvi -- -e 012c
# Decode a hex-encoded varuint
cargo run --example uvi -- -d ac02The crate has 160 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 error_tests
cargo test --test integration_tests
cargo test --test proptest_tests
cargo test --test security_tests
# Run benchmarks
cargo bench
# Run the example
cargo run --example uvi -- -e 012cLinting and formatting:
cargo fmt --check
cargo clippy --all-targets --all-features -- -D warningsA build script generates the codec enum from table.csv at the crate root. The build script build.rs parses the CSV. It checks that all codes and names are unique. It emits src/table_gen.rs, which the build includes into src/codec.rs.
To update the table:
- Replace
table.csvwith the latest version from the official multicodec repository. You can also apply your local additions. - Run
cargo build. The build script regeneratessrc/table_gen.rs. - Run
cargo testto make sure the new codecs work.
If the CSV has duplicate codes or names, the build fails. The error names the offending row.
serde(default). Enables serde serialization and deserialization. When on,CodecimplementsSerializeandDeserialize. Strings are used in human-readable formats. Varint bytes are used in binary formats.
[dependencies]
multi-codec = { version = "1.1", default-features = false }#![deny(unsafe_code)]is set at the crate root.- All conversions check input ranges. Negative signed integers are rejected.
- Deserialization rejects varint input longer than 19 bytes. This protects against DoS.
TryFrom<&[u8]>rejects trailing bytes after the codec varint. It returnsError::TrailingData. This prevents silent data loss. UseCodec::try_decode_fromto parse from a stream with trailing data.- All errors return
Result. No path panics on invalid input. - All types are
Send + Syncwith no shared mutable state.
This repo: @dhuseby.
Original author: @gnunicorn.
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.
MIT OR Apache-2.0 (c) Cryptid Technologies