{
 "slug": "ethclipper-address-poisoning-defeating-zero-width-space-middle",
 "topic_id": "TOPIC-076",
 "cluster": "Digital Asset Custody",
 "tier": "Tier B",
 "title": "Address poisoning works because humans check the ends",
 "summary": "Nobody reads a forty-character address. They check the first four and the last four, which is exactly the check an attacker can defeat by brute force.",
 "lede": "Truncated display was a usability decision made when addresses were long and screens were small. It created a verification habit that costs nothing to defeat and is now the basis of an entire attack class.",
 "date": "2024-08-14",
 "category": "Crypto",
 "author_id": "constance-ibe-whitmore",
 "tags": [
  "address poisoning",
  "vanity addresses",
  "UX security",
  "wallet",
  "truncation",
  "homograph"
 ],
 "image_title": "Address Poisoning Checking Ends",
 "schema": "Article",
 "key_takeaways": [
  "Matching a short prefix and suffix is computationally cheap, so the check most people perform provides little assurance.",
  "Displaying a truncation is a lossy operation, and the security of the check depends entirely on what was elided.",
  "Masking is acceptable when the full value is inside the signed statement; truncation with nothing behind it is not."
 ],
 "body": [
  {
   "type": "h2",
   "text": "The arithmetic of a vanity match"
  },
  {
   "type": "diagram",
   "kind": "chain",
   "alt": "The attacker seeds a lookalike address into the victim's history, then waits for it to be copied",
   "caption": "The attack never intercepts anything. It places a plausible wrong option where the user will reach for it.",
   "nodes": [
    {
     "label": "Observe history",
     "sub": "frequent counterparties",
     "note": "public"
    },
    {
     "label": "Grind a vanity match",
     "sub": "4 front, 4 back",
     "note": "minutes"
    },
    {
     "label": "Send dust",
     "sub": "appears in history",
     "note": "seeding",
     "bad": true
    },
    {
     "label": "Victim copies it",
     "sub": "from history",
     "note": "funds gone",
     "bad": true
    }
   ]
  },
  {
   "type": "p",
   "html": "Generating an address with chosen leading and trailing characters is a search. Each additional character multiplies the work by the size of the character set, and the base cost is low."
  },
  {
   "type": "table",
   "head": [
    "Characters matched",
    "Relative search effort"
   ],
   "rows": [
    [
     "4 total (2 front, 2 back)",
     "Trivial — seconds"
    ],
    [
     "8 total (4 front, 4 back)",
     "Cheap — commodity hardware, minutes to hours"
    ],
    [
     "12 total (6 front, 6 back)",
     "Meaningful but attainable"
    ],
    [
     "16 total (8 front, 8 back)",
     "Expensive"
    ],
    [
     "Full address",
     "Infeasible"
    ]
   ]
  },
  {
   "type": "p",
   "html": "The second row is the common display truncation. It is within reach of anyone with a graphics card and patience, which is why this attack is executed at volume rather than against individual high-value targets."
  },
  {
   "type": "h2",
   "text": "How the poisoning happens"
  },
  {
   "type": "ol",
   "items": [
    "The attacker observes a target's transaction history and identifies frequent counterparties.",
    "They generate an address matching the counterparty's visible prefix and suffix.",
    "They send a tiny or zero-value transaction from it to the target, so it appears in the history.",
    "The target later copies an address from their history — or from a wallet's suggestion list — and picks the wrong one.",
    "Funds go to the attacker."
   ]
  },
  {
   "type": "p",
   "html": "Step four is the key. The attack does not need to intercept anything; it needs to place a plausible wrong option somewhere the user will reach for it."
  },
  {
   "type": "h2",
   "text": "Character-level tricks on labels"
  },
  {
   "type": "p",
   "html": "A related family attacks the human-readable name rather than the address."
  },
  {
   "type": "ul",
   "items": [
    "Zero-width characters inserted into a label so two names render identically",
    "Characters from different scripts that look the same as Latin ones",
    "Trailing whitespace that makes a name appear to match",
    "Directional override characters that reverse the displayed order of text"
   ]
  },
  {
   "type": "p",
   "html": "Each of these makes two distinct strings look identical to a person. Normalising and comparing at the codepoint level catches them; visual comparison does not."
  },
  {
   "type": "h2",
   "text": "Masking versus truncation"
  },
  {
   "type": "p",
   "html": "There is an important distinction between hiding characters for readability and discarding them."
  },
  {
   "type": "code",
   "text": "# Truncation — the elided characters are gone from the signed data\n  \"to\": \"0x9c1f...a83e\"\n  → the signature covers only what is shown\n  → a different full address could produce the same display\n\n# Masking — the full value is present, display is abbreviated\n  \"to\": \"0x9c1f8a3e2b7d4056891fa2c73e1b9d40a2f8c31e\",\n  \"to_display\": \"0x9c1f…8c31e\",\n  → the signature covers the full address\n  → a verifier compares the full value, not the abbreviation"
  },
  {
   "type": "p",
   "html": "The rule that follows: abbreviate for display, sign the whole thing, and verify against the whole thing. Never let the abbreviated form be the authoritative value."
  },
  {
   "type": "h2",
   "text": "Interface changes that help"
  },
  {
   "type": "table",
   "head": [
    "Change",
    "Effect"
   ],
   "rows": [
    [
     "Show the full address, paginated or wrapped, for first-time recipients",
     "Removes the truncated-check habit where it matters most"
    ],
    [
     "Label known counterparties from a user-curated list, not from transaction history",
     "History is attacker-writable; a curated list is not"
    ],
    [
     "Flag first-time recipients explicitly",
     "The attack depends on a new address looking familiar"
    ],
    [
     "Normalise and compare labels at codepoint level",
     "Catches zero-width and homograph tricks"
    ],
    [
     "Warn when a new address closely resembles a known one",
     "This resemblance is the attack's signature"
    ]
   ]
  },
  {
   "type": "p",
   "html": "The second row is the highest-value change and the least implemented. Populating an address book from received transactions means an attacker can write to your address book."
  },
  {
   "type": "h2",
   "text": "For institutional flows"
  },
  {
   "type": "p",
   "html": "Where counterparties are known, the problem is more tractable and the answer is stricter: an allow-list of full addresses, and a separate signed process for adding to it."
  },
  {
   "type": "p",
   "html": "Adding a counterparty becomes the gated event rather than each payment. That is the right place for the control, because the address change is the moment of exposure and payments to an established address are routine."
  },
  {
   "type": "h2",
   "text": "The arithmetic of a vanity match"
  },
  {
   "type": "p",
   "html": "Matching chosen characters is a search, and the base cost is low. Each additional character multiplies the work by the size of the character set, which is what makes the standard display truncation exactly the wrong length."
  },
  {
   "type": "table",
   "caption": "Search effort by characters matched",
   "head": [
    "Characters matched",
    "Effort"
   ],
   "rows": [
    [
     "4 total (2 front, 2 back)",
     "Trivial — seconds"
    ],
    [
     "<strong style=\"font-weight:600\">8 total (4 front, 4 back)</strong>",
     "<strong style=\"font-weight:600\">Cheap — a graphics card and some patience</strong>"
    ],
    [
     "12 total",
     "Meaningful but attainable"
    ],
    [
     "16 total",
     "Expensive"
    ],
    [
     "Full address",
     "Infeasible"
    ]
   ]
  },
  {
   "type": "p",
   "html": "A related family attacks the label rather than the address: zero-width characters inserted into a name, characters from other scripts that render identically, trailing whitespace, directional override characters. Each makes two distinct strings look the same to a person. Normalising and comparing at the codepoint level catches them; looking catches none of them."
  },
  {
   "type": "h2",
   "text": "Objections and honest limits"
  },
  {
   "type": "p",
   "html": "<strong style=\"font-weight:600\">“Our users are told to check the full address.”</strong> They are told, and they check the ends, because the interface shows the ends. Behaviour follows the display. Change the display for first-time recipients rather than the guidance."
  },
  {
   "type": "p",
   "html": "<strong style=\"font-weight:600\">“The address book solves this.”</strong> Only if it is user-curated. An address book populated from transaction history means an attacker can write to your address book by sending you dust."
  }
 ],
 "faq": [
  {
   "q": "Why is matching a prefix and suffix cheap?",
   "a": "It is a search whose cost grows with the number of characters matched. Eight total characters is minutes to hours on commodity hardware."
  },
  {
   "q": "How does the wrong address get into the user's history?",
   "a": "The attacker sends a tiny or zero-value transaction from the lookalike address, so it appears in the history the user later copies from."
  },
  {
   "q": "What is the difference between masking and truncation?",
   "a": "Masking keeps the full value in the signed data and abbreviates the display. Truncation signs only what is shown, so a different address could produce the same display."
  },
  {
   "q": "What is the highest-value interface change?",
   "a": "Populating the address book from a user-curated list rather than transaction history. Otherwise an attacker can write to your address book."
  },
  {
   "q": "How cheap is a matching address?",
   "a": "Eight total characters — the usual display truncation — is minutes to hours on a single graphics card. That is why the attack runs at volume rather than against individuals."
  },
  {
   "q": "How does the wrong address get into my history?",
   "a": "The attacker sends a tiny or zero-value transaction from the lookalike address. It then appears in the history you later copy from."
  },
  {
   "q": "What should institutional flows do instead?",
   "a": "Gate the addition of a counterparty rather than each payment. The address change is the moment of exposure; payments to an established address are routine."
  }
 ],
 "sources": [
  {
   "t": "MetaMask — address poisoning scams",
   "u": "https://support.metamask.io/privacy-and-security/staying-safe-in-web3/address-poisoning-scams/"
  },
  {
   "t": "Unicode Technical Standard #39 — Security Mechanisms",
   "u": "https://www.unicode.org/reports/tr39/"
  },
  {
   "t": "Blockchain address encoding and checksum specifications."
  }
 ],
 "related": [
  {
   "slug": "bybit-wazirx-multisig-hack-payload-mismatch-postmortem",
   "title": "The payload flip",
   "category": "Crypto"
  },
  {
   "slug": "account-masking-ap-systems-directly-enables-wire",
   "title": "Account masking in AP systems",
   "category": "Vertical"
  },
  {
   "slug": "hardware-wallet-blind-signing-ledger-trezor-screens-cant",
   "title": "Why hardware wallet screens cannot protect you",
   "category": "Crypto"
  }
 ],
 "image": "https://cdn.twc.sh/images/igcache/Address%20Poisoning%20Checking%20Ends/1500_900/blog.jpg",
 "wordcount": 1027,
 "url": "/blog/ethclipper-address-poisoning-defeating-zero-width-space-middle.html",
 "reading_time": "5 min read",
 "hub": {
  "slug": "topics/digital-asset-custody",
  "title": "Digital asset custody"
 },
 "answer": "Because nobody reads forty hexadecimal characters. People check the first four and the last four, and generating an address that matches eight chosen characters costs minutes on commodity hardware. The attacker then places the lookalike in the victim's own transaction history, where it will later be copied.",
 "answer_q": "Why does address poisoning work?",
 "entities": [
  {
   "name": "Ethereum",
   "type": "Thing",
   "url": "https://ethereum.org/",
   "primary": true
  }
 ],
 "glossary": [
  {
   "term": "Address poisoning",
   "def": "Seeding a lookalike address into a victim's transaction history so it is later copied in place of the real counterparty's."
  },
  {
   "term": "Vanity address",
   "def": "An address generated by brute-force search to contain chosen characters, typically at the start and end."
  },
  {
   "term": "Homoglyph",
   "def": "A character from one script that renders identically to one from another, so two distinct strings look the same."
  }
 ],
 "checklist": {
  "title": "Interface changes that actually help",
  "id": "interface",
  "desc": "Five changes, in order of value.",
  "steps": [
   {
    "name": "Curate the address book manually.",
    "text": "Never populate it from received transactions. This is the single highest-value change and the least implemented."
   },
   {
    "name": "Show the full address for first-time recipients.",
    "text": "Paginated or wrapped. Remove the truncated-check habit where it matters."
   },
   {
    "name": "Flag first-time recipients explicitly.",
    "text": "The attack depends on a new address looking familiar."
   },
   {
    "name": "Normalise and compare labels at codepoint level.",
    "text": "Catches zero-width and homograph tricks that visual comparison cannot."
   },
   {
    "name": "Warn when a new address resembles a known one.",
    "text": "That resemblance is the attack's signature."
   }
  ]
 },
 "cta": {
  "title": "Where this fits in Manav",
  "html": "Manav's rule is that masking is permitted only when the full value is inside the signed statement. Abbreviate for display, sign the whole thing, verify against the whole thing — never let the abbreviation be the authoritative value.",
  "href": "../docs.html",
  "label": "See statement rules"
 }
}