export const metadata = {
  title: 'Representing SHA-256 Hashes As Avatars',
  description:
    'How to turn 256 bits of entropy into a beautiful, avatar-friendly circular SVG: the Hashvatar.',
  publicationDate: '2021-04-18',
  tags: ['cryptography', 'svg']
}

import {
  InteractiveAvatar,
  AdjustableRadiusFactorSHA256Avatar
} from 'ui/components/hashvatar.client'
import { SHA256Avatar } from 'ui/components/hashvatar.server'
import { FiAlertTriangle } from 'react-icons/fi'

<Note status="warning" title="Public Service Announcement" >
  Since writing this article, I have moved from X <em>(formerly known as Twitter)</em> to Mastodon. [Follow me](https://mamot.fr/@Franky47) there
  instead.

<small>
  <em>PS: Screw you, Elon.</em>
</small>

</Note>

If you [follow me on Twitter](https://twitter.com/fortysevenfx), you may be aware
of my weird [weekend projects](https://twitter.com/search?q=%40fortysevenfx%20%22weekend%20project%22).

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](https://centralized-coin.com)
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](https://barro.github.io/2018/02/avatars-identicons-and-hash-visualization/)
out there: WordPress and GitHub identicons, [robohash](https://robohash.org/), 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:<br/><small className="text-gray-500">
Change the input below to see how the SHA-256 hash changes the render</small>

import { ThemeControls } from 'ui/components/theme-controls'

<section className="-mb-6 flex items-center justify-end text-sm">
  <em className="text-gray-500">Toggle dark mode:</em>
  <ThemeControls className="ml-2" />
</section>

<figure className="mb-8">
  <InteractiveAvatar
    width="16rem"
    height="16rem"
    className="mx-auto my-8"
    variant="stagger"
  />
</figure>

If you are allergic to maths and trigonometry, feel free to play with the more
detailed [interactive example here](/hashvatar). 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):

```tsx
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:

<SHA256Avatar
  className="mx-auto my-8"
  radiusFactor={0}
  showGrid
  showSections={false}
/>

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:

<SHA256Avatar
  className="mx-auto my-8"
  radiusFactor={1}
  showGrid
  showSections={false}
/>

<Note title="Math Details" status="info">
  Equal areas are calculated by solving a system of equations.
  <ol>
    <li>
      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 <code>π/32</code>.
    </li>
    <li>
      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: <code>Pi R^2 - Pi r^2</code>, then we iterate from the outside
      in.
    </li>
  </ol>
  <a
    href="https://www.desmos.com/calculator/cenpmgvqdy"
    className="!-mb-4 block text-sm"
  >
    More details
  </a>
</Note>

Not very pleasing either. How about a mix of both ?<br/><small>(Play with the slider to blend between equal radii and equal areas)</small>

<figure className="mx-auto mb-8 max-w-xs">
  <AdjustableRadiusFactorSHA256Avatar
    className="mx-auto"
    showGrid
    showSections={false}
  />
</figure>

I don't know about you, but `0.42` hits the ballpark both in aesthetics and
[nerd-sniping satisfaction](https://xkcd.com/356/), 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!")`:

```txt
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:

```txt
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
```

<SHA256Avatar
  className="mx-auto my-8"
  showGrid
  showLabels
  showSections={false}
  hash="315f5bdb76d078c43b8ac0064e4a0164612b1fce77c869345bfc94c75894edd3"
/>

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.

<Note>
  The reason we can get away with all sections going to the center is because of
  our mapping order: by laying out from the outside in, the inner sections will
  be overlaid on top of the outer ones in z-index.
</Note>

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

```tsx {31-36}
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()`](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/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)

```ts
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:

<section className="-mb-6 flex items-center justify-end text-sm">
  <em className="text-gray-500">Toggle dark mode:</em>
  <ThemeControls className="ml-2" />
</section>

<SHA256Avatar
  className="mx-auto my-8"
  hash="315f5bdb76d078c43b8ac0064e4a0164612b1fce77c869345bfc94c75894edd3"
  mapColor={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
    const l = 40 + 30 * normalizedL
    return `hsl(${h}, ${s}%, ${l}%)`
  }}
/>

<Note title="About Accessibility" status="info">
  The colour mapping function could use a high-contrast version that focuses on
  the Luminosity channel rather than the Hue.
</Note>

### 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.

1. We XOR all the bytes of the hash together to compute the **hash soul**
2. For each ring, we XOR the bytes that form this ring's section to compute the **ring's soul**. _[(horcruxes?)](/horcrux)_

```ts
// 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.

```ts {8-10}
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:

<section className="-mb-6 flex items-center justify-end text-sm">
  <em className="text-gray-500">Toggle dark mode:</em>
  <ThemeControls className="ml-2" />
</section>

<figure className="mb-8">
  <div className="mb-2 flex justify-around">
    <SHA256Avatar
      className="h-24 w-24 sm:h-32 sm:w-32"
      hash="315f5bdb76d078c43b8ac0064e4a0164612b1fce77c869345bfc94c75894edd3"
      mapColor={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
        const l = 50 + 20 * normalizedL
        return `hsl(${h}, ${s}%, ${l}%)`
      }}
    />
    <SHA256Avatar
      className="h-24 w-24 sm:h-32 sm:w-32"
      hash="315f5bdb76d078c43b8ac0064e4a0164612b1fce77c869345bfc94c75894edd3"
      variant="normal"
    />
    <SHA256Avatar
      className="h-24 w-24 sm:h-32 sm:w-32"
      hash="315f5bdb76d078c43b8ac0064e4a0164612b1fce77c869345bfc94c75894edd3"
      variant="stagger"
    />
  </div>
  <div className="flex justify-around text-center text-sm">
    <p className="w-24 sm:w-32">Without souls</p>
    <p className="w-24 sm:w-32">With souls</p>
    <p className="w-24 sm:w-32">With souls &amp; staggering</p>
  </div>
</figure>

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:

<figure className="mb-8">
  <div className="mb-2 flex justify-around">
    <SHA256Avatar
      className="h-24 w-24 sm:h-32 sm:w-32"
      hash="2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae"
      variant="stagger"
    />
    <SHA256Avatar
      className="h-24 w-24 sm:h-32 sm:w-32"
      hash="fcde2b2edba56bf408601fb721fe9b5c338d10ee429ea04fae5511b68fbf8fb9"
      variant="spider"
    />
    <SHA256Avatar
      className="h-24 w-24 sm:h-32 sm:w-32"
      hash="34707c3f40dfa20c3902b807b627d420d6d474d9d98066ba637953d1cfd6b914"
      variant="flower"
    />
    <SHA256Avatar
      className="h-24 w-24 sm:h-32 sm:w-32"
      hash="851cfc3d60d379af774e1e92dbd0648dd2f512ef1894ccd182ec5e05239b6f50"
      variant="gem"
    />
  </div>
  <div className="flex justify-around text-center text-sm">
    <p className="w-24 sm:w-32">Normal</p>
    <p className="w-24 sm:w-32">Spider</p>
    <p className="w-24 sm:w-32">Flower</p>
    <p className="w-24 sm:w-32">Gem</p>
  </div>
</figure>

## 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](https://twitter.com/wzulfikar) for the name)_
in action in the
[Centralized Coin Explorer](https://centralized-coin-explorer.vercel.app),
or play with the variants yourself [on the playground](/hashvatar).

I will publish the code as an NPM package later, in the mean time the source
[code for this article](https://github.com/franky47/francoisbest.com/blob/next/src/pages/posts/2021/hashvatars.mdx)
is on GitHub, as well as the
[component itself](https://github.com/franky47/francoisbest.com/blob/next/src/components/SHA256Avatar.tsx).

[Follow me on Mastodon](https://mamot.fr/@Franky47) for updates and more weekend projects.

<Note>
  I see a lot of people asking me whether they can use the code above for NFT
  art projects. The answer is <strong>no</strong>.

You may have missed the part <em>"without the crypto overhead"</em> 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.

</Note>
