DEV Community

Cover image for Wallet address spoofing attacks: Nobody Reads 42 Characters
Dmitry Mandrika
Dmitry Mandrika

Posted on Edited on Originally published at Medium

Wallet address spoofing attacks: Nobody Reads 42 Characters

Address poisoning and clipboard hijackers make the same bet: that you look at the first few characters of an address, the last few, and nothing in between. We wrote a small open-source library that turns the whole address into a picture, and then worked out what it costs to fake one.

The real recipient and the attacker's lookalike from the 3 May 2024 case. Same first four and last six hex digits.

On 3 May 2024 someone sent 1,155 WBTC, about $68 million that day, to a stranger.

They had been careful. They first sent a 0.05 ETH test payment to the right address, and only then moved the real amount. In between, an attacker dropped a worthless transfer into their history from an address that started with 0xd9A1 and ended with 853a91, exactly like the one they had just paid. When the victim went back to the history to copy the recipient, there were two entries that read 0xd9A1…3a91. They picked the wrong one. (CoinDesk, Blockaid)

The story has an unusual ending: after a week of on-chain messages and a 10% bounty offer, the attacker returned about 22,960 ETH. Most victims don't get a sequel.

I'm Dmitry. I build SoikaWallet, a non-custodial desktop wallet with an air-gapped signer that lives on a phone. When we reviewed our own send screen against this attack, the honest summary was short: our only defence was showing the full address and hoping the user reads it. This article is about why that is not a defence, what the attacks look like up close, and the library we wrote because of it.

The swap that happens after you press Ctrl+C

The older of the two attacks is the clipboard hijacker, or clipper. It sits on the machine, watches the clipboard, and when something that looks like a crypto address appears, it replaces it with the attacker's. You copy one address and paste another.

It is not new and it is not rare. Kaspersky's CryptoShuffler took 23 BTC this way back in 2017. Unit 42 described ComboJack in 2018; it polled the clipboard every half a second. The same year Bleeping Computer took apart a sample that worked with more than 2.3 million addresses. In February 2019 ESET reported the first clipper on Google Play, dressed up as MetaMask. In 2023 Kaspersky counted about 16,000 detections in 52 countries of a trojanized Tor Browser with a clipper inside, and roughly $400,000 stolen that year. On 17 June 2026 Microsoft and Check Point each published a fresh clipper campaign. Same day, unrelated malware.

The early clippers had a weakness: the pasted address looked nothing like the copied one, and a user who glanced at the first characters noticed. So the malware learned to pick a replacement that looks right. The one Microsoft described substitutes a Bitcoin address that begins with the same two characters as yours. Crude, but it's the right idea.

Laplas Clipper, sold by subscription since 2022, sends the address you copied to its server and gets back a lookalike. Bleeping Computer tested it: the returned Bitcoin address matched the first and last few characters of the original, and came back in about five seconds.

And the swap no longer has to happen in the clipboard. It moved into the application:

  • In September 2025 a phished npm maintainer account pushed malicious versions of chalk, debug and sixteen other packages with over two billion weekly downloads between them. The payload ran in the browser, hooked fetch, XMLHttpRequest and window.ethereum, and rewrote any address it saw in page data or in an outgoing transaction. It chose the replacement from 280 hardcoded addresses by Levenshtein distance, that is, the one that looked most like the original. It was caught within two hours and earned almost nothing. The design is what matters.
  • In April 2025 ReversingLabs found an npm package that patched the installed Atomic and Exodus wallets on disk, so that the genuine wallet UI redirected outgoing funds. Removing the package did not undo it.
  • Doctor Web described counterfeit Android phones shipped with a modified WhatsApp that swaps addresses inside messages. The sender's screen shows the right address. The recipient's shows the thief's.

Different delivery, one assumption: the human at the end of the chain will compare a few characters at each end and approve.

The transfer you never asked for

Address poisoning needs no malware at all. Everything happens on-chain, in public.

The attacker watches for transfers. You pay Alice. Within minutes your history gains a new row involving an address that begins and ends like Alice's. Next time you pay Alice, you open the history, copy "her" address, and it isn't hers.

Researchers at Carnegie Mellon, in a USENIX Security 2025 paper, sort the bait into three kinds:

  • Dust. A tiny amount of a real token sent from the lookalike address. In their data 98.8% of these are under $3.
  • Zero-value transfers. ERC-20 transferFrom with amount 0 needs no allowance, so anyone can emit a Transfer event from your address to their lookalike. In your history it looks like something you sent.
  • Counterfeit tokens. A fake contract with the symbol "USDT" can emit whatever events its author likes.

Their numbers for two years of Ethereum and BSC: 270 million poisoning transfers, 17.4 million targeted addresses, 6,633 payments that went to a lookalike, at least $83.8 million lost. The WBTC case isn't even in that total; their detector didn't follow that token. The success rate on Ethereum was about 0.01%. One in ten thousand is plenty when a try costs cents. Chainalysis dissected the campaign behind the WBTC theft: 82,031 seeded lookalike addresses, and even after giving the $68 million back the operator cleared about $1.49 million, a return of more than 1,100%.

A history list with a poisoned row on top. Left: what most wallets show. Right: the same rows with an hh picture per address.

It got worse when it got cheaper. After Ethereum's Fusaka upgrade in December 2025 cut fees, Blockaid counted 628,000 poisoning attempts in November 2025 and 3.4 million in January 2026. Coin Metrics estimated that dust had become about 11% of all Ethereum transactions, and that the single busiest sender reached over a million addresses for roughly $5,000 in tokens.

On 19 December 2025 a trader withdrew about $50 million in USDT from Binance, sent a 50 USDT test to the right address, and 26 minutes later sent 49,999,950 USDT to a lookalike. Note what that person did: a test transfer. That is the standard advice. The test transfer is, in all likelihood, what told the attacker which address to imitate.

"Check every character" is not advice

Every vendor guide ends the same way: verify the entire address. Trezor's says "carefully check every character". Ledger's, to its credit, adds that "people are not machines: we can be lazy, we can be in a rush".

It is worse than laziness. People who are trying still fail. In an eye-tracking study at CCS 2018, participants were explicitly asked to verify a 64-digit checksum in which only the first ten and last ten digits had been kept. 38% of them missed the mismatch. Tan et al. (CHI 2017) simulated an attacker who matches the beginning and end of a hex fingerprint: 21% of attacks got through. One participant described the method all of us use: "I first checked the last set of numbers, then randomly glanced… until I felt I had verified enough."

Wallets make it easier for the attacker by not showing the middle at all. 0xd9A1…3a91 is a design convention, and the CMU authors think it explains why the biggest poisoning group mined two tiers of lookalikes, one for wallets that show three to five characters at each end and one for explorers that show six or seven. MetaMask said it plainly in June 2026 when it shipped its detection feature: truncation "is the exact blind spot scammers exploit".

How expensive is the lookalike? The open-source profanity2 generator reports 1,361 million addresses per second on one RTX 4090. Matching the first four and last four hex digits is 232 tries. Three seconds. The CMU team estimates that the strongest group they tracked, which matched up to 20 digits, burned about 27,000 GPU-days on it. They still came out ahead.

Why not just use identicons?

That was my first thought too. Wallets already draw little icons next to addresses. Shouldn't the lookalike get a different icon?

Often it doesn't. MetaMask's Jazzicon is, as far as I can read the code, seeded with parseInt(address.slice(2, 10), 16): the first eight hex digits, 32 bits. A lookalike that matches those eight digits gets the same icon, pixel for pixel. Blockies feeds the address string into a small non-cryptographic generator and picks colours from a continuous range; back in 2018 Austin Griffith built a Blockie miner, and a twin good enough to survive a glance turned up after 45 minutes.

We ran our own version of that experiment: 16.7 million random candidates per target, keep the five that look most like it.

Nearest of 2^24 candidates. With Blockies-style icons the overall impression starts to converge. With hh the best candidates share the pattern of empty cells and still differ in two to four colours and three to six figures.

There is a deeper problem, and it applies to us as well. Tan's study also tested pictures: with OpenSSH randomart and abstract Vash images people missed 10–12% of attacks, and with cute unicorn avatars 54%, the worst result in the paper. People compare the gist of an image, and an attacker matches the salient features first. Any visual scheme has to be designed around that, and has to be honest about where it stops.

What we built

Humanized Hash, hh for short, turns an address, a public key or any hash 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 looks a bit like a GitHub identicon, which was the intent. I wanted something calm.

A Sui address has 64 hex digits. Change one and every cell changes, because every cell depends on every bit of the input.

The design rules came out of the usability papers more than out of cryptography:

  • Few categorical values, no gradients, no fine texture. Hsiao et al. found that 24 bits encoded in 8 distinct colours were compared with about 85% accuracy on hard pairs; the same 24 bits in 64 fine colour steps dropped to a coin flip.
  • 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 first. That is the unicorn lesson.
  • Content you can name. "Purple triangle pointing left, top-left corner" works over the phone.
  • It hashes bytes, not strings. The checksummed, lower-case and upper-case spellings of one EVM address give one picture.

Nominally a picture carries 68 bits, 44 of them in shape alone. Our first draft was a 3 × 3 quilt with 16 glyphs. It lost about 30 bits and the glyphs turned to mush at 32 pixels, so it went in the bin.

Next to the picture there is a six-character tag like W2D-2K6. I'll come back to why.

Under the hood there is nothing exotic: SHA-256, HMAC and PBKDF2, with length-prefixed, domain-separated inputs. No invented cryptography. The libraries have no third-party dependencies (we wrote the SHA-256, the deflate and the PNG encoder ourselves), use integer arithmetic only, and produce the same bytes in C++, Kotlin, TypeScript, Go and Python, checked against shared golden vectors. MIT licence. The algorithm has no version and will never change, because a picture a user has learned must stay the same forever.

What a forgery costs

This is the part I care about most, so here is how the numbers were obtained. They are calculations, not a measured attack.

The attacker's speed. We take profanity2's 1.36 billion addresses per second on an RTX 4090 and round up to 1.4 billion. (CMU measured 516 million on an older RTX 8000, so this is generous to the attacker.)

The text. Matching d hex digits takes 16d tries on average. Four at each end: 232, three seconds. Six at each end: 248, 2.3 days. Eight at each end: 264, about 420 years.

The picture. Before anything is drawn, the input goes through 16,384 iterations of PBKDF2-HMAC-SHA-256. For us that is about 7 ms on a desktop core and 12 to 33 ms on a Pixel 5a, once per address, cached. For a forger it is paid on every try, and we estimate it adds about 11 bits of work, which turns 1.4 billion tries per second into roughly 700,000.

Then the question is how many tries. A perfect forgery must match all 16 cells: 268. But people are not perfect comparers, so we also price a sloppy forgery that is allowed to differ from the target in any two cells: about 252.

Expected search time on one GPU, for one target.

So: 252 stretched tries is about 210 GPU-years, 268 is about 14 million. And a forger who needs the ends of the text to match and the picture to match pays the product of the two.

Now the caveats, because there are two big ones.

First, GPUs can be rented by the thousand, and a mass attacker doesn't grind against one victim. The CMU paper shows groups testing each candidate against about a million targets at once, which divides the cost per victim by a million. 210 years divided by a million is under two hours. For a picture everybody can compute, that is uncomfortable.

Second, humans reliably distinguish only about 30 bits of a visual hash, fewer from memory. A picture that looks different proves the address is different. A picture that looks the same is strong evidence and not proof. That is what the tag is for: six characters you can compare exactly, by eye or by voice.

The picture only your wallet can draw

The answer to the first caveat is the second mode, and as far as I know no wallet does this with its identicons today.

A universal picture is the same for everyone. A keyed picture depends on a 32-byte secret of the wallet.

In keyed mode the fingerprint is an HMAC of the stretched digest under a 32-byte secret that belongs to your wallet. An attacker who doesn't hold the key cannot compute the picture you will see. There is nothing to grind against. Their lookalike address will get some random picture, unrelated to the one you know for Alice, and not by 252 but by construction.

Poisoning and clipboard swaps target one wallet at a time, which is exactly the case this covers. The key is derived from the wallet seed on its own path (SLIP-0021), so it needs no backup and a restored wallet draws the same pictures. A nice side effect: mistype your BIP-39 passphrase and every picture is unfamiliar.

Universal mode remains for cases where two parties compare: a receive screen shown to a payer, a payment request, a support chat. Stretching is there to make that mode as expensive to attack as we can afford. 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.

Four colours, chosen for people who see them differently

About one man in twelve has a colour vision deficiency. A security indicator that fails for 8% of male users is a bad indicator, so colour was treated as the secondary channel from the start: 44 of the 68 bits are in shape and position, which survive any kind of colour vision, greyscale and JPEG.

The colours still had to be picked properly. We followed the method from Petroff's "Accessible Color Sequences" (2021): for every pair of colours compute the perceptual distance in CAM02-UCS under normal vision, under protanomaly and deuteranomaly at severities from 10% to 100% (the Machado et al. 2009 model), and under tritanopia (Brettel et al. 1997). The palette's score is its worst pair under its worst condition. Our gate was a minimum of 20, plus WCAG contrast of at least 3:1 against both white and a dark #121212 surface, because wallets have dark themes.

The well-known palettes didn't pass. The first four Okabe-Ito colours scored 12.5 under tritanopia and only 2.25:1 on white. Petroff's own first four failed on contrast. The palette I liked by eye failed too (16.4 under deuteranopia). What shipped is that hand-picked palette nudged by an optimiser, within 12 CAM02-UCS units of the original, until it passed: #7A96C5, #890AF0, #C10445, #D48200. Score 24.1. The weakest pair is red against orange under full deuteranopia.

Simulated with the Machado et al. matrices. In greyscale the colours collapse into two greys, and the shapes carry the picture on their own.

Four colours is less than the eight the literature allows. With that contrast requirement on both light and dark backgrounds, four is what stayed apart for everyone.

Where it sits in SoikaWallet

hh is a standalone library, but we built it for SoikaWallet, and that shaped it. SoikaWallet is a non-custodial, multichain desktop wallet (Bitcoin, EVM, Tron, Solana, Sui, TON) written in C++, with an AirGap mode: the keys live on a phone that never goes online, and transactions travel to it as QR codes, over NFC or Bluetooth.

The picture does two jobs there.

Checking a recipient. The picture stands next to the recipient field on the send screen and updates the moment an address is pasted, then appears again in the confirmation. Saved contacts and history rows carry it too. If a clipper swaps what you pasted, or you copied a poisoned row, the picture next to the field is not Alice's picture. For a first payment to someone new, the wallet shows the universal picture, so you can compare with what the payee sees on their receive screen.

Not getting lost in your own wallet. An HD wallet grows dozens of accounts across chains, all of them 0x… strings. Each account gets its picture in the account list and on the receive screen. After a week you recognise "the one with the orange square in the corner" the way you recognise an app icon. One EVM address has the same picture on every EVM chain, since the chain is deliberately not mixed in.

AirGap signing: two devices, one private picture.

With AirGap there is a third job. The phone decodes the transaction request on its own and draws the recipient again, with the same private picture and the same tag. The offline phone is the screen you trust. If something on the online machine changed the address after you checked it, the two screens disagree, and comparing two pictures side by side takes a second where comparing two 42-character strings across two devices takes patience nobody has.

What it does not do

I'd rather you hear this from me.

  • It doesn't help a user who doesn't look. A picture that is never compared protects nobody. It belongs where the decision is made, at 64 pixels or more, not as a 16-pixel decoration.
  • It doesn't replace the text check. Same-looking pictures are evidence. The tag, or the full address, is certainty.
  • Malware that controls your screen, or the application that draws the picture, is out of scope. If the attacker can paint pixels, they can paint the right ones. That is what the air-gapped second screen is for.
  • Universal pictures can in principle be ground by a well-funded mass attacker. Inside a wallet, use keyed mode.
  • It does nothing for a first-time payment to an address you got from a compromised source. No hash can tell you that an address belongs to Alice. It can only tell you it is the same address as last time.

Try to break it

The code is on GitHub under censync, with the specification, the threat model, the golden vectors and the design sheets, including the ones where our ideas failed.

$ hh_cli 0xd9A1b0B1e1aE382DbDc898Ea68012FfcB2853a91 --out real.png
tag          W2D-2K6

$ hh_cli 0xd9A1C3788D81257612E2581A6ea0aDa244853a91 --out fake.png
tag          X9B-KSW
Enter fullscreen mode Exit fullscreen mode

Two wallet chips that both read 0xd9A1...3a91, and below them two completely different pictures

The real recipient and the attacker's lookalike from the 3 May 2024 case. Same first four and last six hex digits. Pictures drawn by hh.

If you build wallets, explorers or anything else that shows an address to a human, take it. If you do security research, the most useful thing you could do for us is to find a pair of addresses whose pictures a person would confuse, and tell us how long it took. Reports go to dev@censync.com.

And if you just use crypto: stop copying addresses from your transaction history. Keep an address book. The attacker's entire business model is that row.

GitHub sources, libraries: С++, Golang, Kotlin, Python, TypeScrypt

Follow along at soikawallet.app or @SoikaWallet.


Sources

Top comments (0)