---
title: "skill by agenticluke · skilld"
canonical_url: "https://skilld.dev/gh/agenticluke/ethereum-keccak-guard-plus"
meta:
  description: "Prevent Ethereum hash bugs in JavaScript and TypeScript. Node SHA3-256 is not Ethereum Keccak-256. Using it can break selectors, signatures, storage… From agenticluke/ethereum-keccak-guard-plus."
  "og:description": "Prevent Ethereum hash bugs in JavaScript and TypeScript. Node SHA3-256 is not Ethereum Keccak-256. Using it can break selectors, signatures, storage… From agenticluke/ethereum-keccak-guard-plus."
  "og:title": "skill by agenticluke"
  "twitter:description": "Prevent Ethereum hash bugs in JavaScript and TypeScript. Node SHA3-256 is not Ethereum Keccak-256. Using it can break selectors, signatures, storage… From agenticluke/ethereum-keccak-guard-plus."
  "twitter:title": "skill by agenticluke"
---

`

[All skills](https://skilld.dev/skills)

[![agenticluke avatar](https://skilld.dev/_img/avatar?url=https%3A%2F%2Fgithub.com%2Fagenticluke.png%3Fsize%3D96)](https://skilld.dev/gh/agenticluke)

# **/skill**

[@dfdbee1](https://github.com/agenticluke/ethereum-keccak-guard-plus/commit/dfdbee163ad78dbf62583ff4650690bc8352aa77 "Your agent reads SKILL.md at commit dfdbee1")

by [agenticluke](https://skilld.dev/gh/agenticluke)· [agenticluke](https://skilld.dev/gh/agenticluke)/ [ethereum-keccak-guard-plus](https://skilld.dev/gh/agenticluke/ethereum-keccak-guard-plus)

Prevent Ethereum hash bugs in JavaScript and TypeScript. Node SHA3-256 is not Ethereum Keccak-256. Using it can break selectors, signatures, storage slots, and address creation with no warning.

- 1 file
- 6.6 KB
- Updated 2 weeks ago
- [GitHub](https://github.com/agenticluke/ethereum-keccak-guard-plus/blob/dfdbee163ad78dbf62583ff4650690bc8352aa77/skill/SKILL.md "View SKILL.md on GitHub")

## SKILL.md

6.6 KB

**≈50** tokens always: the name and description. **≈1.6k** when used: this file.

## Node.js Keccak-256

Ethereum uses Keccak-256.

Node's `crypto.createHash('sha3-256')` uses NIST SHA3-256. It is a different hash. The same input gives a different result.

### When to Use This Skill

Use this skill when you:

- Make Ethereum function selectors.
- Make event topics.
- Build EIP-712 data.
- Build Merkle trees.
- Find Solidity storage slots.
- Make an address from a public key.
- Review Ethereum code that uses Node `crypto`.

### Main Rule

Never use this for Ethereum hashing:

```
crypto.createHash('sha3-256')
```

Use a clear Keccak-256 helper from `ethers`, `viem`, `web3.js`, or another trusted package.

### Why This Matters

Node gives no warning when the wrong hash is used.

```
import crypto from 'node:crypto';
import { keccak256, toUtf8Bytes } from 'ethers';

const data = 'hello';

const sha3 = crypto.createHash('sha3-256').update(data).digest('hex');
const keccak = keccak256(toUtf8Bytes(data)).slice(2);

console.log(sha3 === keccak); // false
```

### Pick the Right Input Form

Hash bytes, not a value that only looks like bytes.

- Use UTF-8 bytes for plain text.
- Use hex bytes for data that starts with `0x`.
- Use ABI encoding when Solidity uses `abi.encode`.
- Use packed encoding only when Solidity uses `abi.encodePacked`.
- Keep the exact type list and value order used by Solidity.

Do not hash the text `"0x1234"` when you mean the bytes `0x12, 0x34`.

```
import { getBytes, keccak256, toUtf8Bytes } from 'ethers';

const textHash = keccak256(toUtf8Bytes('0x1234'));
const byteHash = keccak256(getBytes('0x1234'));

console.log(textHash === byteHash); // false
```

### ethers v6

```
import {
  id,
  keccak256,
  solidityPackedKeccak256,
  toUtf8Bytes,
} from 'ethers';

const byteHash = keccak256(new Uint8Array([0x01, 0x02]));
const textHash = keccak256(toUtf8Bytes('hello'));

const topic = id('Transfer(address,address,uint256)');

const packedHash = solidityPackedKeccak256(
  ['address', 'uint256'],
  ['0x742d35Cc6634C0532925a3b8D4C9B569890FaC1c', 100n],
);
```

### viem

```
import { keccak256, stringToBytes } from 'viem';

const hash = keccak256(stringToBytes('hello'));
```

### web3.js

```
const hash = web3.utils.keccak256('hello');

const packedHash = web3.utils.soliditySha3(
  {
    type: 'address',
    value: '0x742d35Cc6634C0532925a3b8D4C9B569890FaC1c',
  },
  { type: 'uint256', value: '100' },
);
```

Check your `web3.js` version before hashing hex strings. Some helpers may treat a `0x` value as bytes instead of text.

### Concrete Example: Function Selector

The selector for `transfer(address,uint256)` is the first four bytes of its Keccak-256 hash.

```
import { id } from 'ethers';

const signature = 'transfer(address,uint256)';
const selector = id(signature).slice(0, 10);

console.log(selector); // 0xa9059cbb
```

The signature must use exact ABI types. Do not add argument names or spaces.

```
id('transfer(address,uint256)');          // correct
id('transfer(address to,uint256 amount)'); // wrong
```

### Event Topic and Type Hash

```
import { id, keccak256, toUtf8Bytes } from 'ethers';

const eventTopic = id('Transfer(address,address,uint256)');

const typeHash = keccak256(
  toUtf8Bytes('Transfer(address from,address to,uint256 value)'),
);
```

Names matter in an EIP-712 type string. Field order and spacing must match the type you mean to use.

For full EIP-712 messages, use the package's EIP-712 helper. Do not join fields by hand.

### Solidity Mapping Slot

Solidity mapping slots use normal ABI encoding, not packed encoding.

```
import { AbiCoder, keccak256 } from 'ethers';

function getAddressMappingSlot(
  key: string,
  mappingSlot: bigint,
): string {
  const encoded = AbiCoder.defaultAbiCoder().encode(
    ['address', 'uint256'],
    [key, mappingSlot],
  );

  return keccak256(encoded);
}
```

Nested mappings and dynamic keys use more steps. Follow Solidity's storage rules for the exact field type.

### Public Key to Address

Ethereum uses the 64-byte `x || y` part of an uncompressed secp256k1 public key. Drop the first `0x04` byte, hash the rest, and keep the last 20 bytes.

```
import { getBytes, keccak256 } from 'ethers';

function pubkeyToAddress(publicKey: string): string {
  const bytes = getBytes(publicKey);

  if (bytes.length !== 65 || bytes[0] !== 0x04) {
    throw new Error('Expected a 65-byte uncompressed public key');
  }

  const hash = keccak256(bytes.slice(1));
  return `0x${hash.slice(-40)}`;
}
```

Do not slice a compressed 33-byte public key. Decompress it first with a trusted secp256k1 package.

Use a checksum helper such as `getAddress` if you need a checksummed address.

### Merkle Tree Checks

A Merkle proof only works when every part matches:

- Leaf encoding
- Pair order
- Pair sorting
- Byte joining
- Hash function

Do not guess these rules. Copy them from the smart contract or its clear spec.

Packed encoding can be unsafe when two or more dynamic values are joined. Different inputs can make the same packed bytes. Use normal ABI encoding unless the contract calls for packed encoding.

### Code Review

Use `rg` when it is installed:

```
rg -n "createHash\s*\(\s*['\"]sha3-256['\"]" \
  --glob '*.js' --glob '*.ts' --glob '!node_modules/**' .

rg -n "keccak256|soliditySha3|solidityPackedKeccak256" \
  --glob '*.js' --glob '*.ts' --glob '!node_modules/**' .
```

Also check aliases and wrappers around `createHash`.

For each hash call, confirm:

1. The code uses Keccak-256.
2. Text and hex data are turned into the right bytes.
3. ABI types and value order match Solidity.
4. Normal and packed ABI encoding are not mixed up.
5. The output size is cut only when the rule calls for it.
6. A known test value is checked.

### Test Known Values

Keep at least one known result in your tests.

```
import { id, keccak256, toUtf8Bytes } from 'ethers';
import { expect, test } from 'vitest';

test('uses Ethereum Keccak-256', () => {
  expect(keccak256(toUtf8Bytes(''))).toBe(
    '0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470',
  );

  expect(id('transfer(address,uint256)').slice(0, 10)).toBe(
    '0xa9059cbb',
  );
});
```

### Final Rule

In Ethereum code, use an explicit Keccak-256 helper. Never replace it with Node SHA3-256. Then check the input bytes and encoding, since the right hash with the wrong input is still wrong.

Source: [SKILL.md on GitHub](https://github.com/agenticluke/ethereum-keccak-guard-plus/blob/dfdbee163ad78dbf62583ff4650690bc8352aa77/skill/SKILL.md)

## Third-party checks

No third-party reports yet.

## Provenance

[Signed by skilld at dfdbee1.](https://github.com/agenticluke/ethereum-keccak-guard-plus/commit/dfdbee163ad78dbf62583ff4650690bc8352aa77 "dfdbee163ad78dbf62583ff4650690bc8352aa77") This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 2 weeks ago.

Activeupdated 2 weeks ago

## Capability

<dl>

<dt>origin</dt>
<dd>ECC direct-port adaptation</dd>

<dt>version</dt>
<dd>1.0.0</dd>

</dl>

## README badge

![README badge for agenticluke/ethereum-keccak-guard-plus](https://skilld.dev/b/agenticluke/ethereum-keccak-guard-plus?theme=light&label=0)

## Related skills

-
-
-
-
-
-