Contents

swift-libp2p/swift-multihash

Swift implementation of Multihash -> Self identifying hashes

Table of Contents

- Example - API

Overview

Multihash is a protocol for differentiating outputs from various well-established cryptographic hash functions, addressing size + encoding considerations.

For more details see
Note:

This package currently doesn't support Blake2b, Blake2s & Blake3. If you're up for the challenge, please feel free to add support!

Install

Include the following dependency in your Package.swift file

let package = Package(
    ...
    dependencies: [
        ...
        .package(url: "https://github.com/swift-libp2p/swift-multihash.git", .upToNextMinor(from: "0.3.0"))
    ],
    ...
        .target(
            ...
            dependencies: [
                ...
                .product(name: "Multihash", package: "swift-multihash"),
            ]),
    ...
)

Usage

Example


import Multihash

/// Multihash Format
/// <uvarint hash function code><uvarint digest size in bytes><hash function output>

HashFunction.allCases
/// [.identity, .md5, .sha1, .sha2_256, .sha2_512, .sha3_224, .sha3_256, .sha3_384, .sha3_512,
///  .keccak_224, .keccak_256, .keccak_384, .keccak_512]

/// Hashing
let multihash = try Multihash(hashing: "multihash", with: .sha1)
multihash.asString(base: .base16)         // -> "111488c2f11fb2ce392acb5b2986e640211c4690073e"
multihash.asString(base: .base32PadUpper) // -> "CEKIRQXRD6ZM4OJKZNNSTBXGIAQRYRUQA47A===="
multihash.asString(base: .base58btc)      // -> "5dsgvJGnvAfiR3K6HCBc4hcokSfmjj"
multihash.asString(base: .base64Pad)      // -> "ERSIwvEfss45KstbKYbmQCEcRpAHPg=="

/// Hashing bytes never throws
let multihash = Multihash(hashing: payload, with: .sha2_256)
multihash.asString(base: .base16)         // -> "12209cbc07c3f991725836a3aa2a581ca2029198aa420b9d99bc0e131d9f3e2cbe47"
multihash.asString(base: .base58btc)      // -> "QmYtUc4iTCbbfVSDNKvtQqrfyezPPnFvE33wFmutw9PBBk"

/// Or wrap a digest you already have, without re-hashing it.
/// Throws if the digest is longer than the hash function can produce.
let digest = Array("multihash".utf8).sha1()
let mh = try Multihash(digest: digest, function: .sha1)

/// The parts are parsed once, at init, and are non-optional
mh.code                                   // -> 0x11        (UInt64)
mh.digestLength                           // -> 20          (Int)
mh.digest                                 // -> ArraySlice, zero-copy
mh.algorithm                              // -> Codecs.sha1
mh.hashName                               // -> "sha1"      (nil for codes outside the table)
mh.hashFunction                           // -> HashFunction.sha1

/// Decoding a Multihash

/// From a multibase compliant string. The base prefix is required, not guessed at.
///
/// "f111488c2f11fb2ce392acb5b2986e640211c4690073e"
///     f       11      14     88c2f11fb2ce392acb5b2986e640211c4690073e
/// <base16> <sha1> <20 bytes> <sha1 digest>
let mh = try Multihash(multibase: "f111488c2f11fb2ce392acb5b2986e640211c4690073e")

/// From a buffer that is exactly one multihash
let mh = try Multihash(buffer)            // any Collection<UInt8>: Array, ArraySlice, Data, …

/// From the front of a larger buffer — a CID, a multiaddr component, a framed message.
/// The digest length prefix says where the multihash ends, so the remainder comes back too.
let (mh, remaining) = try Multihash.decode(prefixed: buffer)
let (mh, remaining) = try buffer.multihash()   // same thing, as a Collection method

/// Verifying that a payload matches a hash
mh.matches(payload)                       // -> Bool, false if unverifiable
try mh.matching(payload)                  // throws .unsupportedHashFunction instead

API

Every throwing member below is throws(MultihashError). Multibase failures arrive wrapped as .invalidMultibase(MultibaseError), with the underlying cause preserved:

do {
    let mh = try Multihash(multibase: string)
} catch {
    switch error {                                   // `error` is a MultihashError
    case .invalidMultibase(let cause): …             // and `cause` is a MultibaseError
    case .digestTooLongForHashFunction(_, let expected, let actual): …
    default: …
    }
}

// or match a specific nested cause directly
catch MultihashError.invalidMultibase(.unknownBase) { … }

/// Hashing, non-throwing when using a HashFunction, throwing when using a Codec
Multihash(hashing: some Collection<UInt8>, with: HashFunction, truncatedTo: Int? = nil)
Multihash(hashing: some Collection<UInt8>, codec: Codecs, truncatedTo: Int? = nil) throws
Multihash(hashing: String, with: HashFunction, using: String.Encoding = .utf8, truncatedTo: Int? = nil) throws
Multihash(hashing: String, codec: Codecs, using: String.Encoding = .utf8, truncatedTo: Int? = nil) throws

/// Wrapping a precomputed digest.
/// Both throw if the digest is longer than the hash function can produce, shorter digests are allowed. 
/// A digest of the right length still proves nothing about the content, only `matching(_:)` against the original bytes does that.
Multihash(digest: some Collection<UInt8>, function: HashFunction) throws
Multihash(digest: some Collection<UInt8>, codec: Codecs) throws

/// Decoding
Multihash(_: some Collection<UInt8>) throws                        // the whole buffer is one multihash
Multihash.decode(prefixed:) -> (multihash: Multihash, remaining: SubSequence) throws
Collection<UInt8>.multihash() -> (multihash: Multihash, bytes: SubSequence) throws
Multihash(multibase: String) throws                                // prefix required
Multihash(multibaseDigest: String, code: Codecs) throws            // a bare digest, not a multihash
// ^ both report a bad multibase string as MultihashError.invalidMultibase(_:)

/// Properties, parsed once at init, non-optional
Multihash.value: [UInt8]          // the whole buffer, prefixes included
Multihash.code: UInt64
Multihash.digestLength: Int
Multihash.digest: ArraySlice<UInt8>   // a slice of `value`, no copy
Multihash.algorithm: Codecs?          // nil for codes outside the Multicodec table
Multihash.hashName: String?           // nil for codes outside the Multicodec table
Multihash.hashFunction: HashFunction? // nil if this package can't compute it

/// Strings
Multihash.asString(base: BaseEncoding, withMultibasePrefix: Bool = false) -> String
Multihash.description       // the base58btc form, e.g. "QmYtUc4iTC…"
Multihash.debugDescription  // "Multihash: sha1 0x11 20 88c2f11f…"

/// Verification
Multihash.matches(_: some Collection<UInt8>) -> Bool
Multihash.matching(_: some Collection<UInt8>) throws -> Bool  // distinguishes unverifiable from mismatched

/// HashFunction
HashFunction.allCases: [HashFunction]
HashFunction(codec: Codecs)   // -> HashFunction?
HashFunction.codec: Codecs
HashFunction.name: String
HashFunction.digestLength: Int?    // nil for .identity, whose output is as long as its input
HashFunction.hash(_: some Collection<UInt8>) -> [UInt8]

/// Multihash is a value type conforming to Sendable, Hashable, Codable,
/// RandomAccessCollection<UInt8> and ContiguousBytes, so it can be passed
/// anywhere bytes are expected:
/// - Data(mh)
/// - buffer.writeBytes(mh)
/// - try Multihash(otherMH)
///
/// NOTE: because of that, `count` and `first` refer to the entire buffer,
/// prefixes included. Use `digest` (or `digestLength`) for the digest alone.

Contributing

Contributions are welcomed! This code is very much a proof of concept. I can guarantee you there's a better / safer way to accomplish the same results. Any suggestions, improvements, or even just critiques, are welcome!

Let's make this code better together! 🤝

Credits

License

MIT © 2026 Breth Inc.

Package Metadata

Repository: swift-libp2p/swift-multihash

Default branch: main

README: README.md