# Representing SHA-256 Hashes As Avatars

François Best • 18 April 2021 • 12 min read

If you follow me on Twitter, you may be aware of my weird weekend projects.

They are little challenges I give myself, usually without too many stakes involved, and with small enough a scope so that I can ship it in a day or two, while keeping spare family time.

This weekend's project is to build a transaction explorer for the Centralized Coin experiment. Something like a blockchain explorer, but without the crypto overhead.

Each node in the system is represented by a hash. Because humans are terrible at reading and quickly identifying large numbers (other than by their first or last few digits), a visual representation is needed.

There are many solutions out there: WordPress and GitHub identicons, robohash, monsterID etc..

I wanted one that still looks abstract (not as opinionated as monsters or robots), and that plays nice in a rounded avatar UI component.

This is what I ended up with:

Change the input below to see how the SHA-256 hash changes the render

*Toggle Dark Mode:*

315f5bdb76d078c43b8ac0064e4a0164

If you are allergic to maths and trigonometry, feel free to play with the more detailed interactive example here. Otherwise, let's dive into how it's done.

## Space Partitioning#

Many of the existing solutions produce square images, yet avatars are often displayed as circles. They would lose information on the corners when rounded, so instead of a cartesian (x-y) approach, we're going to use polar (angle-radius) coordinates instead.

A grid in cartesian space maps to concentric circles and pie-like cuts in polar space.

SHA-256 hashes have 256 bits of information that we need to represent. Dividing a circle into 256 sections would make each section too small to be visually useful, and would only leave 1 bit of "value" to represent in each section (0 or 1, black or white).

Instead, we're going to divide the circle into 32 sections:

- 4 concentric rings
- 8 pie-like cuts

The resulting SVG code for such a grid looks like this (in a React component):

```
export const SHA256Avatar = () => {
// Equal radii
const r1 = 1
const r2 = r1 * 0.75
const r3 = r1 * 0.5
const r4 = r1 * 0.25
return (
<svg viewBox="-1 -1 2 2">
<circle cx={0} cy={0} r={r1} />
<circle cx={0} cy={0} r={r2} />
<circle cx={0} cy={0} r={r3} />
<circle cx={0} cy={0} r={r4} />
<line x1={-r1} x2={r1} y1={0} y2={0} />
<line y1={-r1} y2={r1} x1={0} x2={0} />
<line
x1={-r1 * Math.SQRT1_2}
x2={r1 * Math.SQRT1_2}
y1={-r1 * Math.SQRT1_2}
y2={r1 * Math.SQRT1_2}
/>
<line
x1={r1 * Math.SQRT1_2}
x2={-r1 * Math.SQRT1_2}
y1={-r1 * Math.SQRT1_2}
y2={r1 * Math.SQRT1_2}
/>
</svg>
)
}
```

Doing this naively, with each concentric ring's radius being 1/4th of the outermost/largest one gives us this:

There are some issues: the innermost ring sections are tiny compared to the outermost.

If we calculate the radii so that each section has an equal area, we get the following result:

- Each section area is 1/32nd of the area of the whole circle. Assuming the outer circle has a radius of 1, that's an area of
`π/32`

. - To compute the associated radius for a ring, we express the pie slice area with the outer radius R and subtract the pie slice area with the inner radius r:
`Pi R^2 - Pi r^2`

, then we iterate from the outside in.

Not very pleasing either. How about a mix of both ?

(Play with the slider to blend between equal radii and equal areas)

Equal Radii

Equal Areas

Blend factor: 1.00

I don't know about you, but `0.42`

hits the ballpark both in aesthetics and
nerd-sniping satisfaction, so let's go for that.

## Section Mapping#

Now that we have 32 nice looking sections on our circle, we can map each section to an 8-bit value in the hash.

As an example, let's take the following hash, the output of `sha256("Hello, world!")`

:

```
315f5bdb76d078c43b8ac0064e4a0164612b1fce77c869345bfc94c75894edd3
```

We can split it in 32 blocks of 8 bits (2 hexadecimal digits), and organise them by 4 blocks of 8 to map to the rings:

```
12 o'clock -> clockwise
31 5f 5b db 76 d0 78 c4 Outer ring
3b 8a c0 06 4e 4a 01 64 Middle-outer ring
61 2b 1f ce 77 c8 69 34 Middle-inner ring
5b fc 94 c7 58 94 ed d3 Inner ring
```

In order to fill each section with a different colour, we generate an SVG `<path>`

polygon.
Each section resembles a pie/pizza slice, going from the center
of the circle to a given radius, and covering 1/8th of the circle.

Since SVG uses cartesian coordinates, we'll have to convert our polar logic into cartesian SVG path commands:

```
interface Point {
x: number
y: number
}
function polarPoint(radius: number, angle: number): Point {
// Angle is expressed as [0,1[
// Note: we subtract pi / 2 to start at noon and go clockwise
// Trigonometric rotation + inverted Y axis = clockwise rotation, nifty!
return {
x: radius * Math.cos(2 * Math.PI * angle - Math.PI / 2),
y: radius * Math.sin(2 * Math.PI * angle - Math.PI / 2)
}
}
function moveTo({ x, y }: Point) {
return `M ${x} ${y}`
}
function lineTo({ x, y }: Point) {
return `L ${x} ${y}`
}
function arcTo({ x, y }: Point, radius: number) {
return `A ${radius} ${radius} 0 0 0 ${x} ${y}`
}
const Section = ({ index, radius }) => {
const angleA = index / 8
const angleB = (index + 1) / 8
const path = [
moveTo({ x: 0, y: 0 }),
lineTo(polarPoint(radius, angleA)),
arcTo(polarPoint(radius, angleB), radius),
'Z' // close the path
].join(' ')
return <path d={path} />
}
```

## Colour Mapping#

Now we can turn each section's byte value into a colour.

For that, we have many options. 8 bits could map directly to 256 colours like the
old Windows systems, but that would require a lookup table. Instead, we can
generate colours using the `hsl()`

CSS function.

Hue is the component that has the most visual impact, while Saturation and Lightness can be used to add little variants to each section.

To map our 8 bit value to 3 components, we can divide the byte into:

- 4 bits for the Hue (16 values)
- 2 bits for the Saturation (4 values)
- 2 bits for the Lightness (4 values)

```
function mapValueToColor(value) {
const colorH = value >> 4
const colorS = (value >> 2) & 0x03
const colorL = value & 0x03
const normalizedH = colorH / 16
const normalizedS = colorS / 4
const normalizedL = colorL / 4
const h = 360 * normalizedH
const s = 50 + 50 * normalizedS // Saturation between 50 and 100%
const l = 40 + 30 * normalizedL // Lightness between 40 and 70%
return `hsl(${h}, ${s}%, ${l}%)`
}
```

We can adjust the range for each component to get nice results:

*Toggle Dark Mode:*

### Order, Chaos & Soul#

Our colour encoding suffers from a flaw: two hashes can look very similar, but have a few bits of difference here and there. They can go unnoticed especially if differences occur in the LSBs of hue/saturation/lightness components.

Also, the sections look random in colour, and the whole avatar lacks coherence.

It would be nice if there was some pattern to a hash that makes it random enough to be distinguished yet coherent enough within itself. A balance between order and chaos.

In order to fix that, we compute the **soul** of the hash, using XOR operations.

- We XOR all the bytes of the hash together to compute the
**hash soul** - For each ring, we XOR the bytes that form this ring's section to compute the
**ring's soul**.*(horcruxes?)*

```
// Each soul is between -1 and 1
function computeSouls(bytes: string[]) {
const ringLength = Math.round(bytes.length / 4)
const rings = [
bytes.slice(0, ringLength),
bytes.slice(1 * ringLength, 2 * ringLength),
bytes.slice(2 * ringLength, 3 * ringLength),
bytes.slice(3 * ringLength, 4 * ringLength)
]
const xorReducer = (xor: number, byte: string) => xor ^ parseInt(byte, 16)
return {
hashSoul: (bytes.reduce(xorReducer, 0) / 0xff) * 2 - 1,
ringSouls: rings.map(ring => (ring.reduce(xorReducer, 0) / 0xff) * 2 - 1)
}
}
// Example for our demo hash:
// hashSoul: 0.2313725490196079
// ringSoul 0: 0.9137254901960785
// ringSoul 1: -0.8274509803921568
// ringSoul 2: -0.050980392156862786
// ringSoul 3: -0.9529411764705882
```

These values give us additional parameters to play with in the colour calculation.

Notably, we can *"seed"* the Hue with the hash soul, and introduce hue varitions
per-ring with each ring soul, and with the value itself.

```
export function mapValueToColor({ value, hashSoul, ringSoul }) {
const colorH = value >> 4
const colorS = (value >> 2) & 0x03
const colorL = value & 0x03
const normalizedH = colorH / 16
const normalizedS = colorS / 4
const normalizedL = colorL / 4
const h = 360 * hashSoul
+ 120 * ringSoul
+ 30 * normalizedH
const s = 50 + 50 * normalizedS
const l = 40 + 30 * normalizedL
return `hsl(${h}, ${s}%, ${l}%)`
}
```

We can also introduce structural variations by changing each ring's starting angle based on the ring soul, to create a staggering effect:

*Toggle Dark Mode:*

Without souls

With souls

With souls & staggering

Not only does this help give a bit more uniqueness to the avatar, it also helps with accessibility for colour-blind people

## A Bit Of Fun#

If we change the radius and flags for the arc part of the section paths and play with each ring's starting angle, we can obtain interesting variations:

Normal

Spider

Flower

Gem

## Going Further#

With a bit of tweaking in the colour mapping value, we can easily extend this technique to arbitrary hash lengths (as long as said length is divisible by 32).

It so happens that when I started this project, Centralized Coin was using SHA-256, but later on switched to SHA-384, which gives 12 bits per section.

## Conclusion#

You can see the hashvatars *(thanks to @wzulfikar for the name)*
in action in the
Centralized Coin Explorer,
or play with the variants yourself on the playground.

I will publish the code as an NPM package later, in the mean time the source code for this article is on GitHub, as well as the component itself.

Follow me on Twitter for updates and more weekend projects.

**no**.

You may have missed the part

*"without the crypto overhead"*in the introduction. If your solution to something in life involves a blockchain, the most overkill way of storing data ever devised, then you haven't given the problem enough attention.