Blockchain In Rust #01: Blocks & Hashing — Companion Guide

Explore the inner workings of cryptocurrencies while learning the basics of Rust programming

encody
GeekLaunch
5 min readFeb 17, 2019

--

Welcome to the first post in this companion guide to the Blockchain In Rust video series! We’ll be walking through the development of a simple, cryptocurrency-like blockchain project. These posts will be conceptual in nature, whereas the videos are generally code focused. They are a companion guide, rehashing concepts discussed in the videos.

That said, let’s jump in.

“Blockchain”?

It’s a fancy-schmancy technical buzzword that magically makes companies more valuable.

It’s eating Wall Street.

It can hold lettuce, oranges, electricity, cats, and, of course, money.

Let’s not get ahead of ourselves; the generic, PoW-capable blockchain we’re building is simply a chronological, sequential list of blocks, so let’s start with those.

Blocks

For now, they contain this information:

  • Index — This block’s location in the blockchain.
  • Payload — The information that this block contains. Later, this field will hold a list of transactions, but for now we will just consider it arbitrary data.
  • Timestamp — This links the information stored in the payload to a particular point in time.
  • Nonce — We’ll use this later in the post about mining.
  • Previous Block Hash — This blockchain will be continually built up over time. This field cryptographically links one block to the previous one.
  • Hash — The hash of this block, which will subsequently be in the previous block hash field of the next block.

Hold up, what’s a hash?

Unlike what you may have had for breakfast, a hash is the result of a set of computations (the hashing algorithm) performed on some data, producing an identifier or “fingerprint” for that data. It’s very important that running a hash algorithm on the same data will always produce the same resulting hash. There are many different hashing algorithms, but the ones we’re interested in will have the following properties:

  • It is well-nigh impossible to find two pieces of data that have the same hash.
  • It is very easy to calculate the hash of any data.
  • It is impossible to predict the effect changing the source data will have on the resulting hash without, of course, actually running the hashing algorithm.

Here’s a sample of what the hashes for the same piece of data (the byte string "GeekLaunch") look like for different hashing algorithms:

MD5("GeekLaunch") = "e76485e55ba4c16aac30bd446b73d96e"
SHA1("GeekLaunch") = "c333e84f729c67d6b591e056e1b51e0077a9c030
SHA256("GeekLaunch") =
"a17d5669f2148e2982baab7c0b4c7d81100c7cf52c45a8d7deb429aeba156ea6"

The long strings of numbers and letters after the equals signs are the hashes generated by the respective algorithm, encoded in hexadecimal notation.

The first two algorithms, MD5 and SHA1, are no longer considered secure, but remain non-ideally popular. Please do not use them in new projects. SHA256 is still considered secure, and since Bitcoin uses it, so will we.

Hashing Blocks

We’ve defined six fields in our block structure so far: index, payload, timestamp, nonce, previous block hash, hash. In order to calculate the hash of a given block, we’re going to use the bytes constituting the first five of these fields, and then store the hash of those bytes in the last field. (We’ll get to why hashing is important in the first place in the post about mining.) Quite simply, the algorithm for hashing a block:

  1. Squish all the bytes together into one big lump.
  2. Feed the lump to the SHA256 hash function.

Turns out that’s it! This will produce a unique hash identifier for every distinct block.

Interesting bits of code

We’re using the Rust programming language for this series, but don’t worry — you don’t have to know Rust to follow along! You can look at the complete code for this post+video here.

We’re going to define a struct to represent blocks:

type BlockHash = Vec<u8>; // vector of bytespub struct Block {
pub index: u32,
pub timestamp: u128,
pub hash: BlockHash,
pub prev_block_hash: BlockHash,
pub nonce: u64,
pub payload: String,
}

In English, this says: “Data of type Block will contain six fields: an unsigned 32-bit integer called index, an unsigned 128-bit integer called timestamp, a BlockHash type called hash (where BlockHash is a vector of bytes), another BlockHash type called prev_block_hash, an unsigned 64-bit integer called nonce, and a String called payload.” Right now, the payload is just a String, but later it will be a list of transactions.

We’ll be hashing more things later, so let’s define a trait called Hashable:

pub trait Hashable {
fn bytes (&self) -> Vec<u8>; // function returns vector of bytes
// ^ this semicolon = no function body
fn hash (&self) -> Vec<u8> {
crypto_hash::digest(
crypto_hash::Algorithm::SHA256,
&self.bytes(),
)
}
}

This allows us to provide a hash (&self) -> Vec<u8> function on different structs. All the struct has to do is provide a bytes (&self) -> Vec<u8> function. Looking back to our two-step algorithm, bytes (&self) -> Vec<u8> is squishing the bytes together, and hash (&self) -> Vec<u8> is feeding the lump to SHA256. The crypto_hash::digest(...) call computes, as you might expect, the hash of the bytes provided by the &self.bytes() call using the SHA256 algorithm.

Now we can set up hashing on whatever structs we want! All we have to do is implement Hashable and provide a bytes (&self) -> Vec<u8> function body.

Let’s do that for our Block struct. To implement a trait, we use the impl keyword, so it looks a little something like this:

impl Hashable for Block {
fn bytes (&self) -> Vec<u8> {
let mut bytes = vec![]; // empty, mutable vector
bytes.extend(&u32_bytes(&self.index));
bytes.extend(&u128_bytes(&self.timestamp));
bytes.extend(&self.prev_block_hash);
bytes.extend(&u64_bytes(&self.nonce));
bytes.extend(self.payload.as_bytes());
bytes // implicit return. Same as `return bytes;`
}
}

The first line tells the Rust compiler that what is enclosed between that first set of curly braces will provide everything necessary for Block to implement the trait Hashable. Remember, structs implementing Hashable only have to provide a bytes (&self) -> Vec<u8> function, so that’s exactly what we do.

Luckily, the body of the bytes (&self) -> Vec<u8> function here isn’t too difficult to read. We’re just creating a vector called bytes and extending it with the byte representations of each of the five fields we discussed above.

(Note: the uX_bytes(...) functions are defined here.)

Now, to wrap this up nice and cleanly, all we have to do to get the hash value for a Block is call its hash (&self) -> Vec<u8> function!

let block = Block { ... };
let h = block.hash();
println!("{:?}", &h); // e.g. [34, 187, ..., 21]

Conclusion

Now that we’ve got hashing under our belts, let’s see what we can do with mining!

You can find the code for this installment in its entirety on GitHub.

Happy coding!

--

--