跳转至

为什么以太坊地址是 20 字节?从私钥到地址的全解析

1. 以太坊地址多大?

以太坊地址大小:20 字节(bytes) = 160 位(bits),对外通常写成:0x + 40 个十六进制字符

  • 每个十六进制字符是 4 bit
  • 40 × 4 = 160 bit = 20 byte

例如:0x28816c4c4792467390c90e5b426f198570e29307,去掉 0x,剩下的 40 个 Hex 字符,就是 20 字节的二进制地址。

2. 从密钥到地址:为什么是 20 字节?

以太坊账户的生成流程(外部账户 EOA)大致是这样:

  1. 生成一个 私钥(256 bit 随机数);
  2. 用 secp256k1 椭圆曲线算出 公钥(64 字节:x、y 各 32 字节);
  3. 对公钥做一次 Keccak-256 哈希:得到 32 字节;
  4. 取哈希结果的后 20 字节(右边 20 字节),作为地址;
┌──────────────────────────────────────────┐
│          私钥 Private Key             │
│     256-bit 随机数(32 字节)           │
└──────────────────────────────────────────┘
                      │
                      ▼
┌──────────────────────────────────────────┐
│         公钥 Public Key               │
│ secp256k1 椭圆曲线推导出 64 字节坐标     │
│  公钥 = (x: 32 bytes, y: 32 bytes)    │
└──────────────────────────────────────────┘
                      │
                      ▼
┌──────────────────────────────────────────┐
│      Keccak-256 哈希(32 字节)        │
│ hash = keccak256(uncompressed_pubkey)│
└──────────────────────────────────────────┘
                      │
                      ▼
┌──────────────────────────────────────────┐
│       取哈希结果的最后 20 字节           │
│     (右 20 字节 = 160 bit)           │
└──────────────────────────────────────────┘
                      │
                      ▼
┌──────────────────────────────────────────┐
│         以太坊地址 Ethereum Address    │
│   0x + 40 位十六进制字符(= 20字节)     │
└──────────────────────────────────────────┘

所以地址本质上是: address = last_20_bytes( keccak256( public_key ) )

问题:为什么不用 32 字节,非要截断成 20 字节?

简单理解:截断一点 hash,当地址用,够用 + 好用 + 节省资源

(1)短一点,方便展示与输入

  • 32 字节地址 → 64 个 Hex 字符

  • 20 字节地址 → 40 个 Hex 字符

从前端 UI、钱包显示、用户复制粘贴的体验来看,40 个字符已经很长了,64 个更难用。

(2)安全性仍然足够

地址空间有 2¹⁶⁰ ≈ 1.46 × 10⁴⁸ 种可能,即使全地球算力爆炸式提高,要「碰撞」一个指定地址也几乎不可能。

真正的安全性瓶颈是:私钥是否真的随机 & 是否被泄露,而不是「地址够不够长」。

(3)存储空间更小

状态树(state trie)、交易编码里地址要存很多次,20 字节 × N 次,比 32 字节 × N 次省空间、省带宽、省存储。

3. Solidity 里的 address 实际占多少字节?

这里要分 “概念上的大小”“EVM 实际存储的大小” 两层看。

概念上:address = 20 字节

在 Solidity 类型系统里:

address         // 20 字节
address payable // 也是 20 字节,只是多了转账函数

存储在 EVM storage 里:占用 32 字节槽位

EVM 的最小存储单位是 一个 storage slot = 32 字节(256 bit),即使只存一个 bool / uint8 / address,它默认也占用一个完整 32 字节槽(除非编译器做变量打包)。

contract Store {
    address public owner;    // slot 0,占 32 字节
    uint256 public value;    // slot 1,占 32 字节
}

虽然 address 实际只用掉 20 字节,其余 12 字节是填充的(通常为 0),但:

  • storage 层面:1 个 address = 1 个 slot = 32 字节
  • 语义层面:address 还是代表 20 字节的地址

这也就是为什么有时候讲「存一个地址,gas 很贵」,因为它会触发一次 SSTORE 到新槽位。

如果变量打包,例如:

contract Packed {
    address public owner;   // 20 bytes
    uint96  public count;   // 12 bytes
}
  • 20 + 12 = 32 字节
  • 编译器会把 ownercount 塞进同一个 slot → 依然是 32 字节一个槽,但容纳了两个字段

结论:

  • 单独看类型:address = 20 bytes;
  • 存在 storage:一般占 1 个 32-byte slot(除非 packing);

4. 在 ABI / calldata / memory 里的地址大小

Solidity 对外编码(ABI)时候,有一个规则:所有值类型按 32 字节对齐

所以在以下场景中:

  • 函数参数的 ABI 编码(calldata);
  • 函数返回值的 ABI 编码;
  • event topics(日志里);

一个 address 通常是这么表示的:前面 12 字节补 0 + 后面 20 字节地址

例如地址:0x28816c4c4792467390c90e5b426f198570e29307,在 ABI 编码中,会被放进 32 字节:

0x00000000000000000000000028816c4c4792467390c90e5b426f198570e29307
  ↑                       ↑
12 bytes 0              20 bytes address

编码时,占 32 字节(对齐到 256 bit),语义上,有效载荷仍然是 20 字节。