为什么以太坊地址是 20 字节?从私钥到地址的全解析
1. 以太坊地址多大?
以太坊地址大小:20 字节(bytes) = 160 位(bits),对外通常写成:0x + 40 个十六进制字符
- 每个十六进制字符是 4 bit
- 40 × 4 = 160 bit = 20 byte
例如:0x28816c4c4792467390c90e5b426f198570e29307,去掉 0x,剩下的 40 个 Hex 字符,就是 20 字节的二进制地址。
2. 从密钥到地址:为什么是 20 字节?
以太坊账户的生成流程(外部账户 EOA)大致是这样:
- 生成一个 私钥(256 bit 随机数);
- 用 secp256k1 椭圆曲线算出 公钥(64 字节:x、y 各 32 字节);
- 对公钥做一次 Keccak-256 哈希:得到 32 字节;
- 取哈希结果的后 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 字节
- 编译器会把
owner和count塞进同一个 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 字节。