Skip to content
Blog » Introducing Fluent Hash – A Rust Hashing Library with a Fluent Interface

Introducing Fluent Hash – A Rust Hashing Library with a Fluent Interface

    fluent-hash is a new open source Rust library which I recently published to crates.io (see here). I wanted to try creating and publishing a Rust library and so I thought I’d start with something simple and hopefully useful for other developers. It is a simple and lightweight hashing library which provides a fluent interface for creating and formatting hash values. It builds upon the foundations of the trusted cryptography library Ring (which is currently one of the most popular libraries in the Rust cryptography space), by providing a thin wrapper on top of Ring’s digest module. See the Github link for the new project here.

    In this post I will show you how to import the library into your Rust project and how to use the Hashing, Hash and HashContext types for creating hashes from various data types. I also show you how to format Hash values as bytes or hexadecimal using the provided convenience methods.

    Note that this library is new and has not been audited or formally verified so use at your own risk.

    What is a Fluent Interface?

    From Wikipedia: ‘In software engineering, a fluent interface is an object-oriented API whose design relies extensively on method chaining. Its goal is to increase code legibility by creating a domain-specific language (DSL).’

    This basically means we can make a library easier to use and make the code more readible by designing the library in a way that allows the user to chain simple method calls together. As you will see in this post, fluent-hash uses this design pattern.

    Summary of Types

    The fluent-hash library contains the following types:

    enum Hashing – The hashing algorithm. Supports selecting from the following algorithms: Sha1, Sha256, Sha384, Sha512 and Sha512_256. Implements convenience methods such as hash, hash_vec, hash_str and hash_file for producing digests from various data types. These convenience methods return an instance of the Hash struct.

    struct Hash – A hash value which holds the digest produced by one of the Hashing algorithms. Supports formatting as a byte array, byte vector or a hexadecimal string.

    struct HashContext – A context to be used for multi-step hash calculations. Useful when hashing a data structure with multiple fields or when hashing larger inputs.

    See the full Rust library documentation here.

    Getting Started

    To get started using fluent-hash, add the library as a dependancy to your Rust Cargo.toml file. 

    [dependencies]
    fluent-hash = "0.2.3"

    Rust Imports

    Let’s start by importing the required types into our project. In this example we are importing all the fluent-hash types including the Hashing enum values.

    use fluent_hash::Hashing::Sha1;
    use fluent_hash::Hashing::Sha256;
    use fluent_hash::Hashing::Sha384;
    use fluent_hash::Hashing::Sha512;
    use fluent_hash::Hashing::Sha512_256;
    use fluent_hash::Hashing;
    use fluent_hash::HashContext;
    use fluent_hash::Hash;

    Hashing a Byte Array

    In this example we are using the SHA-1 hashing algorithm and hashing the bytes of the string ‘hello, world’.

    let hash: Hash = Sha1.hash(b"hello, world");

    Hashing a Byte Vector

    Here we run the SHA-256 hashing algorithm on a vector of bytes from the string ‘hello, world’.

    let hash: Hash = Sha256.hash_vec(b"hello, world".to_vec());

    Hashing a String

    Here we run the SHA-384 hashing algorithm on the string ‘hello, world’.

    let hash: Hash = Sha384.hash_str("hello, world");

    Hashing a File

    In this example we create a text file containing the string ‘hello, world’ and then run it through the SHA-512 hashing algorithm.

    let mut file = File::create("file.txt")?;
    file.write_all(b"hello, world")?;
    file.sync_all()?;
    let hash: Hash = Sha512.hash_file("file.txt")?;

    The hash_file method also supports error handling.

    let error: Error = match Sha512.hash_file("notfound.txt") {
        Ok(_) => panic!("Expecting std::io::Error"),
        Err(e) => e
    };

    Using the HashContext

    Here we use the lower level HashContext type to manually pass data into the hash function in chunks. This can be useful when hashing a data structure with multiple fields or when hashing larger inputs.

    let mut ctx: HashContext = Sha512_256.new_context();
    ctx.update(b"hello, world");
    ctx.update(b"more data");
    let hash = ctx.finish();

    Formatting the Hash

    fluent-hash supports formatting hash values as bytes or hexadecimal using the convenience methods provided on the Hash type.

    let bytes: &[u8] = hash.as_bytes();
    let bytes_vec: Vec<u8> = hash.to_vec();
    let hex: String = hash.to_hex();
    println!("bytes = {:?}", bytes);
    println!("bytes_vec = {:?}", bytes_vec);
    println!("hex = {}", hex);

    Fluent Interface Method Chaining

    The fluent interface allows you to use method chaining to easily calculate a hash for a given algorithm returning it in the desired format. The resulting code is clean and readable.

    let result = Hashing::Sha256
        .hash(b"hello, world")
        .to_hex();
    println!("result = {}", result);

    Full Sample Code

    Here is the full code sample for reference. 

    use std::fs::File;
    use std::io::Write;
    use std::io::Error;
    use fluent_hash::Hashing::Sha1;
    use fluent_hash::Hashing::Sha256;
    use fluent_hash::Hashing::Sha384;
    use fluent_hash::Hashing::Sha512;
    use fluent_hash::Hashing::Sha512_256;
    use fluent_hash::Hashing;
    use fluent_hash::HashContext;
    use fluent_hash::Hash;
    
    fn main() -> Result<(), Error> {
        // Hashing a byte array
        let hash: Hash = Sha1.hash(b"hello, world");
    
        // Hashing a byte vector
        let hash: Hash = Sha256.hash_vec(b"hello, world".to_vec());
    
        // Hashing a string
        let hash: Hash = Sha384.hash_str("hello, world");
    
        // Hashing a file
        let mut file = File::create("file.txt")?;
        file.write_all(b"hello, world")?;
        file.sync_all()?;
        let hash: Hash = Sha512.hash_file("file.txt")?;
    
        // Hashing a file supports error handling
        let error: Error = match Sha512.hash_file("notfound.txt") {
            Ok(_) => panic!("Expecting std::io::Error"),
            Err(e) => e
        };
    
        // Using the HashContext
        let mut ctx: HashContext = Sha512_256.new_context();
        ctx.update(b"hello, world");
        ctx.update(b"more data");
        let hash = ctx.finish();
    
        // Format the hash
        let bytes: &[u8] = hash.as_bytes();
        let bytes_vec: Vec<u8> = hash.to_vec();
        let hex: String = hash.to_hex();
        println!("bytes = {:?}", bytes);
        println!("bytes_vec = {:?}", bytes_vec);
        println!("hex = {}", hex);
    
        // Fluent interface supports method chaining
        let result = Hashing::Sha256
            .hash(b"hello, world")
            .to_hex();
        println!("result = {}", result);
    
        Ok(())
    }

    Conclusion

    In this post I introduced the fluent-hash library and showed how you can use it to generate SHA-1 and SHA-2 hashes by using the provided Hashing, Hash and HashContext types. I also explained the fluent interface design pattern and showed how fluent-hash uses method chaining to provide an easy to use interface and supports creating clean and readable code.

    Here are the links to the library and documentation for reference:

    https://github.com/web3-developer/fluent-hash

    https://crates.io/crates/fluent-hash

    https://docs.rs/fluent-hash

    Leave a Reply

    Your email address will not be published. Required fields are marked *