visual-hashing 0.1.1

Human-friendly visual fingerprints for keys and checksums: a nameable 64-emoji BLAKE3 hash (emojihash) and OpenSSH-style drunken-bishop randomart
Documentation

visual-hashing

crates.io docs.rs License

Human-friendly visual fingerprints for keys, checksums, and any byte string you need a person to compare out-of-band — the "is this the right key?" glance.

Comparing 64 hex characters by eye is error-prone and nobody actually does it. visual-hashing renders the same bytes two ways a human can actually check:

  • emojihash — a BLAKE3-XOF digest sliced into 6-bit symbols indexing a fixed, nameable 64-emoji alphabet. Short, glanceable, and speakable (pig duck monkey …), so two people can verify a fingerprint over the phone.
  • randomart — the OpenSSH-style "Drunken Bishop" ASCII-art grid you already know from ssh-keygen -lv.

Both are pure, deterministic, byte-for-byte stable functions of the input.

What it looks like

Given a 32-byte Ed25519 public key, visual-hashing renders:

emojihash   🐷 🦆 🐵 🦋 🍎 🍐 🦊 🐸 🐟 🍒 🍎
  (spoken)  pig duck monkey butterfly apple pear fox frog fish cherries apple

randomart   +--[ED25519 256   ]+
            |      =  .o .    |
            |     = +   o . ..|
            |      * . .   .o+|
            |     . + o     +*|
            |    . o S   . +.=|
            |     o     . . BE|
            |          .   = O|
            |           . o X=|
            |           .*+@O=|
            +----------------+

Flip a single bit of the key and both renderings change completely — that is the point.

Install

cargo add visual-hashing

Usage

use visual_hashing::{emojihash, emojihash_labels, randomart};

let key: &[u8] = b"\x00\x01\x02\x03"; // any bytes: a public key, a file digest, …

// A short, speakable emoji fingerprint (11 digits is the conventional length).
println!("{}", emojihash(key, 11));        // 🍑 🦂 🥥 🦉 🐌 🦀 🌽 🐳 🐻 🍒 🐶
println!("{}", emojihash_labels(key, 11)); // peach scorpion coconut …  (the same digits, named)

// A bigger ASCII-art fingerprint; the label only annotates the header.
println!("{}", randomart(key, "ED25519 256"));

A handful of emoji is enough for a human to spot a mismatch, while staying short enough to print in a CLI banner, a log line, or a chat message:

// Tune the length to the surface: a 6-emoji chip for a tight UI…
assert_eq!(visual_hashing::emojihash(b"hello", 6).split(' ').count(), 6);
// …or the full 11 for a key-verification prompt.

Why

A fingerprint only helps if a human can read it back, so the emoji alphabet favours common animals and then familiar foods over abstract, confusable symbols (no 🜲//). Because both renderings are byte-for-byte deterministic and pinned by a frozen conformance corpus, independent implementations — in any language — agree exactly. That matters when the same key fingerprint must look identical on a CLI, in a server log, and in a mobile app, so a user can compare across all three.

API

Function Returns
emojihash(data, length) length space-joined emoji digits
emojihash_labels(data, length) the same digits as space-joined names
emoji_indices(data, length) the raw 0..64 symbol indices
randomart(data, label) a 17×9 drunken-bishop grid; label annotates the header ("" for none)

EMOJI, LABELS, and ALPHABET_SIZE expose the 64-entry alphabet directly, in case you want to render it yourself.

How it works

emojihash. BLAKE3 in extendable-output (XOF) mode produces exactly ceil(length × 6 / 8) bytes; those bits are consumed six at a time, most-significant first, and each 6-bit symbol (0..64) selects one entry from the alphabet. Using a XOF rather than a truncated fixed hash means any length is well-defined and a prefix of a longer fingerprint is not a shorter one (the whole digest shifts).

randomart. A bishop starts in the centre of a 17×9 grid and makes four diagonal moves per input byte (two bits each), incrementing a visit counter on every square it lands on. Counts render through the OpenSSH character ramp " .o+=*BOX@%&#/^" (a leading space for unvisited cells); the start and end squares are marked S and E.

Stability

The 64-emoji alphabet and the randomart character ramp are a wire contract: once 1.0 ships they will not change, because a fingerprint that renders differently across versions is worse than useless. Pre-1.0 the alphabet is considered stable but reserves the right to fix outright mistakes.

The only dependency is blake3. No unsafe, no I/O, wasm32-friendly.

Provenance

visual-hashing was factored out of gmeow-gts, where the same fingerprints identify embedded transport keys. The crate's conformance corpus is generated by a Python reference implementation and shared across languages, so the renderings are portable beyond Rust.

License

Licensed under either of MIT or Apache-2.0 at your option. © Blackcat Informatics® Inc.