Open-source library · v1.1.0

Humanized Hash (hh)

An address, a public key or a hash as a small deterministic picture that a person can compare at a glance. Made to catch address poisoning and clipboard substitution, which work because people check only the first and last characters of a long string.

MIT licence C++ · Kotlin/Java · TypeScript · Go · Python Zero dependencies Colour-blind safe
Two wallet fields that both read 0xd9A1…3a91; below them the full addresses and two completely different hh pictures, tags W2D-2K6 and X9B-KSW
The real recipient and the attacker's lookalike from the 3 May 2024 case2: the same first four and last six hex digits, unrelated pictures. Drawn by hh.
The problem

Nobody reads 42 characters

On 3 May 2024 someone sent 1,155 WBTC, about $68 million that day, to a stranger1. They had been careful: first a 0.05 ETH test payment to the right address, then the real amount. In between, an attacker dropped a worthless transfer into their history from an address that began with 0xd9A1 and ended with 853a91. When the victim copied the recipient from the history, there were two entries that read 0xd9A1…3a91, and they picked the wrong one2.

$83.8Mlost to address poisoning on Ethereum and BSC, Jul 2022 – Jun 20245
270Mpoisoning transfers aimed at 17.4 million addresses in the same two years5
38%of participants missed a mismatch in a checksum cut to its first and last ten digits29
3 sfor one GPU to find an address with the same first four and last four hex digits14,38

The swap after Ctrl+C

A clipboard hijacker watches the clipboard and replaces a copied address with the attacker's. CryptoShuffler took 23 BTC this way in 201715; a trojanized Tor Browser with a clipper inside was detected about 16,000 times in 52 countries in 202319. Modern clippers pick a replacement that looks right: Laplas Clipper asks its server for a lookalike of the copied address21,22.

The swap no longer has to happen in the clipboard. In September 2025 malicious versions of chalk, debug and sixteen other npm packages rewrote addresses inside web pages and outgoing transactions, choosing the replacement by Levenshtein distance25.

The transfer you never asked for

Address poisoning needs no malware. After you pay someone, an attacker plants a row in your history from an address that begins and ends like theirs: dust, a zero-value transferFrom that needs no allowance, or a counterfeit token5,6. Next time you copy "their" address from the history, it isn't theirs.

After fees fell in December 2025, Blockaid counted 628,000 poisoning attempts in November 2025 and 3.4 million in January 20269. Wallets truncate addresses to a few characters at each end, which is, in MetaMask's words, "the exact blind spot scammers exploit"13.

How it works

A picture that depends on every bit of the address

hh turns the input into a 4 × 4 grid. A cell is empty or holds one solid figure: a square, a circle or a triangle pointing one of four ways, in one of four colours. It hashes bytes, not strings, so the checksummed, lower-case and upper-case spellings of one EVM address give one picture. Next to the picture there is a six-character tag such as TKS-PVH: a check that is certain, short enough to read out over the phone.

One digit changes every cell

A Sui address has 64 hex digits. The second differs from the first in one digit, the third in two.

hh picture of the first Sui address
0xeab3150efcb34ff74930d8f3d491be109070a39e4d380de7737aff5c72a0b6b2
tag B6P-65H
hh picture of the second Sui address, unrelated to the first
0xeab3150efcb34ff74930d8f8d491be109070a39e4d380de7737aff5c72a0b6b2
tag Q60-QKR · one digit changed
hh picture of the third Sui address, unrelated to the other two
0xeab3150efcb34ff74930d8f3d491be109010a39e4d380dc7737aff5c72a0b6b2
tag ZSJ-7BK · two digits changed

Designed from the usability research

  • Few categorical values. No gradients or fine texture: people compare a few distinct colours far better than fine colour steps31.
  • Equal salience. Every cell has the same size and the same kinds of content, so there is no single dominant feature for a forger to match first30.
  • Content you can name. "Purple triangle pointing left, top-left corner" works over the phone.
  • Stretched on purpose. Before anything is drawn, the input goes through 16,384 iterations of PBKDF2-HMAC-SHA-256: about 7 ms per address on a desktop core, cached once38,39.
  • No invented cryptography. SHA-256, HMAC and PBKDF2 with length-prefixed, domain-separated inputs.

Deterministic to the byte

Integer arithmetic only. The same input gives the same pixels and the same PNG, BMP and JPEG bytes in every implementation, on every platform.

Frozen algorithm

The algorithm has no version and never changes: a picture a user has learned stays the same for ever. Releases follow SemVer and never alter the output.

No dependencies

SHA-256, HMAC, PBKDF2, deflate and the image encoders are part of the library. The library returns RGBA buffers and encoded files.

One set of golden vectors

hh-cpp owns the specification and the golden vectors; every port reproduces them byte for byte in its own tests.

Two modes

Universal and keyed pictures

A universal picture is the same for everyone. It is what two people compare: a receive screen shown to a payer, a payment request, a support chat.

A keyed picture is computed with a 32-byte secret of the wallet. An attacker who does not hold the key cannot compute the picture you will see, so there is nothing to grind against: a lookalike address gets some random picture, unrelated to the one you know. Inside an application keyed pictures are the default38.

The two modes give unrelated pictures on purpose, and keyed pictures carry a frame so you can tell which one you are looking at. Mixing them up can only cause a false alarm, never a false match.

The same address drawn three ways: the universal picture, the keyed picture of wallet A and the keyed picture of wallet B, all different
The same address: universal, keyed for wallet A, keyed for wallet B.
Security

What a forgery costs

By calculation, not a measured attack. One current GPU tries about 1.4 billion addresses per second14; a try against hh also has to compute the stretched digest, which leaves about 680,000 tries per second. The figures are expected search times on one such GPU for a typical picture38.

Expected time to forge a lookalike on one GPU
The forged address has to matchTriesOne GPU
the first 4 and the last 4 hex digits2323 seconds
the first 6 and the last 6 hex digits2482.3 days
the first 8 and the last 8 hex digits264420 years
the universal picture, with two cells allowed to differ252, stretched210 years
the universal picture, in every cell268, stretched14 million years
the ends of the text and the picturethe product of the two
the keyed picturecannot be searched: without the key the picture cannot be computed

Two honest caveats. A mass attacker tests each candidate against many victims at once5, which divides the cost per victim; that is why keyed pictures are the default inside a wallet. And people reliably tell apart only about 30 bits of a visual hash32: a picture that looks different proves the address is different, a picture that looks the same is strong evidence, not proof. The tag or the full address is the check that is certain.

Comparison

Why not just use identicons?

Wallets already draw small icons next to addresses, but a lookalike often gets a lookalike icon. As far as we can read the MetaMask source, Jazzicon is seeded with the first eight hex digits of the address, so an address that matches those digits gets the same icon33. A Blockie miner found a twin good enough to survive a glance after 45 minutes34.

We ran the same experiment on hh: 224 random candidates per target, keeping the five that look most like it. The nearest candidates share the pattern of empty cells and still differ in two to four colours and three to six figures39.

Rows of icons: a target on the left and its five nearest lookalikes; Blockies-style icons look similar, hh pictures do not
Nearest of 224 candidates. With Blockies-style icons the overall impression converges; with hh it does not.
Accessibility

Four colours, chosen for people who see them differently

About one man in twelve has a colour vision deficiency, so colour is the secondary channel: 44 of the picture's 68 nominal bits are in shape and position, which survive any kind of colour vision, greyscale and JPEG38.

The palette was searched with Petroff's method35: the perceptual distance of every pair in CAM02-UCS under normal vision, protanomaly and deuteranomaly from 10% to 100% severity36 and tritanopia37. The gate was a minimum distance of 20 and a contrast of at least 3:1 on white and on a dark #121212 surface. The first four Okabe-Ito colours scored 12.5; the shipped palette scores 24.139.

  • #7A96C5
  • #890AF0
  • #C10445
  • #D48200
The four-colour palette and four pictures under normal vision, simulated protanopia, deuteranopia, tritanopia and in greyscale
The palette and four pictures under normal vision, protanopia, deuteranopia, tritanopia and greyscale. In greyscale the shapes carry the picture on their own.
Where it is used

Anywhere a person checks a long string

Sending and confirming

The recipient's picture stands next to the address field and on the confirmation screen. A swapped or mistyped address changes it completely.

Address books and account lists

Every saved payee and every account carries its picture, so a list is scanned instead of read; 32 to 48 px is enough for recognition.

Two devices of one user

An offline signer and the online device show the same picture for the same address: two screens compared at a glance instead of 64 characters.

Support, screenshots and voice

The six-character tag travels through chat and over the phone; the picture travels in a screenshot.

Documents from a server

The encoders return PNG, BMP or JPEG bytes, so a backend puts the picture into a receipt, an invoice or an email without a graphics library.

Any hash, not only an address

An SSH or PGP key fingerprint, a TLS certificate pin, an API key, the checksum of a backup or of a firmware image.

What it does not do

  • It does not replace the text check. Same-looking pictures are evidence; the tag or the full address is certainty.
  • It does not help a user who does not look. A picture that backs a decision is at least 64 px and stands beside the picture it is compared with.
  • Malware that controls the screen, or the application that draws the picture, is out of scope.
  • It cannot tell you that an address belongs to someone, only that it is the same address as last time.
Get started

Install and quick start

The address below gives the tag TKS-PVH and the same 128 px PNG in every language.

TypeScript and JavaScript

npm install @censync/hh
import { BaseDigest, Fingerprint } from "@censync/hh";

const digest = BaseDigest.ofHex("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed"); // slow: cache it
const fingerprint = Fingerprint.universal(digest); // or Fingerprint.keyed(digest, key)
const image = fingerprint.render(128); // 128 x 128 RGBA pixels

const png: Uint8Array = image.encodePng(); // the same bytes in every implementation
const tag: string = fingerprint.tag; // "TKSPVH", shown as TKS-PVH

Browsers, Node.js 20+, Deno and Bun. Integration guide with React and Web Worker recipes.

Python

pip install humanized-hash
from humanized_hash import BaseDigest, Fingerprint

digest = BaseDigest.of_hex("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed")    # slow: cache it
fingerprint = Fingerprint.universal(digest)      # or Fingerprint.keyed(digest, key)
image = fingerprint.render(128)                  # 128 x 128 RGBA pixels, image.rgba

png: bytes = image.encode_png()                  # or image.save("address.png")
tag: str = fingerprint.tag                       # "TKSPVH", shown as TKS-PVH

Python 3.9+, CPython and PyPy. Also installs the humanized-hash command. Integration guide.

Go

go get github.com/censync/go-hh@v1.1.0
import hh "github.com/censync/go-hh"

digest, err := hh.BaseDigestFromHex("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed") // slow: cache it
if err != nil {
    return err
}
fp := hh.Universal(digest)                          // or hh.Keyed(digest, key)
img, err := hh.Render(fp, 128, hh.RenderOptions{})  // 128 x 128 pixels
if err != nil {
    return err
}
png, err := img.EncodePNG()                         // or img.NRGBA() for the image packages
tag := fp.Tag()                                     // "TKSPVH", shown as TKS-PVH

Go 1.21+. No global state, safe for concurrent use. API reference with runnable examples on pkg.go.dev.

Kotlin and Java

dependencies {
    implementation("io.github.censync:hh:1.1.0")
}
import io.github.censync.hh.*

val digest = BaseDigest.ofHex("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed")  // slow: cache it
val fingerprint = Fingerprint.universal(digest)      // or Fingerprint.keyed(digest, key)
val image = fingerprint.render(128)                  // 128 x 128 pixels

val png: ByteArray = image.encodePng()
val tag: String = fingerprint.tag                    // "TKSPVH", shown as TKS-PVH

JVM 8+ and Android API 24+; Kotlin 1.9+. Integration guide with Android, Compose and desktop recipes.

C++

include(FetchContent)
FetchContent_Declare(hh
    GIT_REPOSITORY https://github.com/censync/hh-cpp.git
    GIT_TAG v1.1.0
)
FetchContent_MakeAvailable(hh)

target_link_libraries(my_app PRIVATE hh::hh)
#include <hh/hh.hpp>

hh::base_digest digest;   // slow (a few milliseconds), public, cache it per address
if (hh::make_base_digest_from_hex("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed", digest) !=
    hh::error_code::ok) { /* not hexadecimal */ }

hh::fingerprint fp;
hh::universal_fingerprint(digest, fp);        // or hh::keyed_fingerprint(digest, key, fp)

hh::image img;
hh::render(fp, 128, hh::render_options{}, img);   // img.rgba: 128 x 128 RGBA pixels

std::vector<std::uint8_t> png;
hh::encode_png(img, png);
std::string tag = fp.tag();                   // "TKSPVH", shown as TKS-PVH: a check that is certain

C++17 with a C ABI; CMake FetchContent or find_package(hh), pkg-config hh. Integration guide.

Source code

GitHub repositories and packages

Five implementations, one output: every port produces the same pictures, tags and encoded files, byte for byte, and its tests check it against a copy of the hh-cpp golden vectors. All are MIT-licensed; the current release is 1.1.0.

Humanized Hash repositories, packages and install commands
Language GitHub repository Package Install Release
C++17, C ABIreference: specification and golden vectors censync/hh-cpp CMake hh::hh, pkg-config hh
GitHub Releases
CMake FetchContent or find_package(hh) v1.1.0
Kotlin and JavaJVM, Android censync/hh-kotlin Maven Central io.github.censync:hh implementation("io.github.censync:hh:1.1.0") v1.1.0
TypeScript and JavaScriptbrowsers, Node.js, Deno, Bun censync/hh-ts npm @censync/hh npm install @censync/hh v1.1.0
Go censync/go-hh pkg.go.dev github.com/censync/go-hh go get github.com/censync/go-hh v1.1.0
Python censync/hh-python PyPI humanized-hash pip install humanized-hash v1.1.0

Documentation

  • Specification: the algorithm, the encoders and the error codes every port implements.
  • Security model: what a picture proves and what it does not, and when to use which mode.
  • Integration guide: sizes, placement and the product rules.
  • Design lab: palette gate, grinding experiments and benchmarks, including the ideas that failed.

Sources

The research behind Humanized Hash and every figure on this page. Links were checked in September 2026; the forgery-cost figures are calculations, not a measured attack.

Address poisoning

  1. CoinDesk. 1,155 WBTC, about $68M, sent to a lookalike address after a 0.05 ETH test. 3 May 2024. coindesk.com/business/2024/05/03/exploiter-steals-68m-worth-of-crypto-through-address-poisoning
  2. Blockaid. A deep dive into address poisoning: the real and the attacker's address of the WBTC case. blockaid.io/blog/a-deep-dive-into-address-poisoning
  3. Cointelegraph. About 22,960 ETH returned after a 10% bounty offer. May 2024. cointelegraph.com/news/wbtc-address-poisoner-all-funds-negotiations
  4. Chainalysis. Anatomy of an address poisoning scam: 82,031 seeded addresses, net profit $1.49M. 2024. chainalysis.com/blog/address-poisoning-scam/
  5. Tsuchiya, Dong, Soska, Christin. Blockchain Address Poisoning. USENIX Security 2025. usenix.org/system/files/usenixsecurity25-tsuchiya.pdf
  6. Etherscan. What is address poisoning: a zero-value transferFrom needs no allowance. info.etherscan.com/what-is-address-poisoning/
  7. CoinDesk. A 50 USDT test, then 49,999,950 USDT to a lookalike. 20 December 2025. coindesk.com/web3/2025/12/20/crypto-user-loses-usd50-million-in-address-poisoning-scam
  8. Protos. The same $50M USDT case. December 2025. protos.com/crypto-trader-loses-50m-usdt-to-address-poisoning-scam/
  9. Blockaid. Address poisoning: the growing threat. 628,000 attempts in November 2025, 3.4 million in January 2026. blockaid.io/blog/address-poisoning-the-growing-threat-draining-millions-from-crypto-users
  10. Coin Metrics. State of the Network, issue 349: dust is about 11% of Ethereum transactions. 2026. coinmetrics.substack.com/p/state-of-the-network-issue-349
  11. Trezor. Address poisoning attacks: “carefully check every character”. trezor.io/support/troubleshooting/coins-tokens/address-poisoning-attacks
  12. Ledger. Address poisoning: “people are not machines”. ledger.com/blog/address-poisoning
  13. MetaMask. Address poisoning detection: truncation “is the exact blind spot scammers exploit”. 17 June 2026. metamask.io/news/address-poisoning-detection
  14. 1inch. profanity2: 1,361 million addresses per second on an RTX 4090, self-reported. github.com/1inch/profanity2

Clipboard and in-application substitution

  1. Kaspersky. CryptoShuffler: 23 BTC stolen through the clipboard. 2017. kaspersky.com/blog/cryptoshuffler-bitcoin-stealer/19976/
  2. Palo Alto Networks Unit 42. ComboJack checks the clipboard every half second. 2018. unit42.paloaltonetworks.com/unit42-sure-ill-take-new-combojack-malware-alters-clipboards-steal-cryptocurrency/
  3. Bleeping Computer. A clipboard hijacker working with over 2.3 million addresses. 2018. bleepingcomputer.com/news/security/clipboard-hijacker-malware-monitors-23-million-bitcoin-addresses/
  4. ESET. The first clipper on Google Play, a fake MetaMask. February 2019. welivesecurity.com/2019/02/08/first-clipper-malware-google-play/
  5. Kaspersky Securelist. Copy-paste heist: a clipper in a trojanized Tor Browser, about 16,000 detections in 52 countries. 2023. securelist.com/copy-paste-heist-clipboard-injector-targeting-cryptowallets/109186/
  6. Kaspersky. About $400,000 stolen through the fake Tor Browser in 2023. kaspersky.com/about/press-releases/new-clipper-malware-steals-us400000-in-cryptocurrencies-via-fake-tor-browser
  7. Cyble. Laplas Clipper asks its server for a lookalike of the copied address. 2022. cyble.com/blog/new-laplas-clipper-distributed-by-smokeloader/
  8. Bleeping Computer. A Laplas test: the returned address matched the first and last characters in about 5 seconds. 2022. bleepingcomputer.com/news/security/new-clipboard-hijacker-replaces-crypto-wallet-addresses-with-lookalikes/
  9. Microsoft Security. A clipper that substitutes a Bitcoin address with the same first two characters. 17 June 2026. microsoft.com/en-us/security/blog/2026/06/17/crypto-clipper-uses-tor-worm-like-propagation-for-persistence-control/
  10. Check Point Research. A Rust clipper with over 15,500 embedded addresses. 17 June 2026. research.checkpoint.com/2026/from-stars-to-upvotes-fake-reputation-fueling-a-crypto-clipboard-hijacker/
  11. Aikido. npm debug and chalk packages compromised: the replacement chosen by Levenshtein distance. 8 September 2025. aikido.dev/blog/npm-debug-and-chalk-packages-compromised
  12. Security Alliance. Incident analysis: 280 hardcoded attacker addresses. September 2025. radar.securityalliance.org/2025-09-npm-supply-chain
  13. ReversingLabs. An npm package that patches installed Atomic and Exodus wallets. April 2025. reversinglabs.com/blog/atomic-and-exodus-crypto-wallets-targeted-in-malicious-npm-campaign
  14. Doctor Web. Counterfeit Android phones with a WhatsApp that swaps addresses inside messages. April 2025. news.drweb.com/show/?i=15002&lng=en&c=5

How people compare strings and pictures

  1. Cherubini et al. Towards Usable Checksums. CCS 2018. mhumbert.com/publications/ccs18.pdf
  2. Tan et al. Can Unicorns Help Users Compare Crypto Key Fingerprints? CHI 2017. blaseur.com/papers/chi2017-fingerprints.pdf
  3. Hsiao et al. A Study of User-Friendly Hash Comparison Schemes. ACSAC 2009. netsec.ethz.ch/publications/papers/visual_hash_acsac09.pdf
  4. Olembo et al. Developing and Testing a Visual Hash Scheme. 2013.
  5. MetaMask extension source, icon-factory.ts: the Jazzicon seed. “Same eight digits, same icon” is our reading of the code, not a published demonstration. raw.githubusercontent.com/MetaMask/metamask-extension/main/ui/helpers/utils/icon-factory.ts
  6. Austin Griffith. Blockie miner: a lookalike after 45 minutes. austingriffith.com/portfolio/blockieminer/

Colour vision

  1. Petroff. Accessible Color Sequences for Data Visualization. 2021.
  2. Machado, Oliveira, Fernandes. A Physiologically-based Model for Simulation of Color Vision Deficiency. 2009.
  3. Brettel et al. 1997: the tritanopia simulation.

Figures from the hh project

  1. hh-cpp, docs/SECURITY.md: 68 nominal bits, 44 in shape; 16,384 PBKDF2 iterations; forgery-cost estimates. github.com/censync/hh-cpp/blob/v1.1.0/docs/SECURITY.md
  2. hh-cpp, docs/design: benchmarks, the grinding experiment and the palette gate. github.com/censync/hh-cpp/tree/v1.1.0/docs/design