Q12: 以太坊主要使用什么哈希函数?
·
欢迎来到《Solidity面试修炼之道》专栏💎。
专栏核心理念:
核心 Slogan💸💸:从面试题到实战精通,你的 Web3 开发进阶指南。
一句话介绍🔬🔬: 150+ 道面试题 × 103 篇深度解析 = 你的 Solidity 修炼秘籍。
- ✅ 名称有深度和系统性
- ✅ "修炼"体现进阶过程
- ✅ 适合中文技术社区
- ✅ 记忆度高,易于传播
- ✅ 全场景适用
Q12: 以太坊主要使用什么哈希函数?
简答:
以太坊主要使用 Keccak-256 哈希函数,这是 SHA-3 的一个变体。在 Solidity 中通过 keccak256() 函数使用。
详细分析:
Keccak-256 是以太坊的核心哈希函数,用于多个关键场景:
主要用途:
- 地址生成:从公钥生成以太坊地址
- 数据完整性:验证数据未被篡改
- Merkle 树:构建状态树、交易树、收据树
- 签名:ECDSA 签名前对消息进行哈希
- 存储槽计算:计算 mapping 和动态数组的存储位置
- 函数选择器:计算函数签名的前 4 字节
Keccak-256 特性:
- 输出长度:256 位(32 字节)
- 单向函数:不可逆
- 抗碰撞:找到两个产生相同哈希的输入在计算上不可行
- 雪崩效应:输入的微小变化导致输出完全不同
注意:以太坊使用的是 Keccak-256,而不是最终标准化的 SHA3-256。它们的输出不同,这是一个历史遗留问题(以太坊在 SHA-3 标准化之前就采用了 Keccak)。
代码示例:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @title Keccak256Examples
* @notice 演示 Keccak-256 哈希函数的使用
*/
contract Keccak256Examples {
/**
* @notice 基本哈希示例
*/
function basicHash(string memory _text) public pure returns (bytes32) {
// 对字符串进行哈希
return keccak256(abi.encodePacked(_text));
}
/**
* @notice 哈希多个参数
*/
function hashMultipleParams(
address _addr,
uint256 _amount,
string memory _message
) public pure returns (bytes32) {
// 将多个参数编码后哈希
return keccak256(abi.encodePacked(_addr, _amount, _message));
}
/**
* @notice 演示雪崩效应
* @dev 输入的微小变化导致完全不同的输出
*/
function demonstrateAvalanche() public pure returns (
bytes32 hash1,
bytes32 hash2
) {
// 两个几乎相同的输入
hash1 = keccak256(abi.encodePacked("Hello"));
hash2 = keccak256(abi.encodePacked("hello")); // 只有大小写不同
// 输出完全不同!
assert(hash1 != hash2);
return (hash1, hash2);
}
/**
* @notice 用途 1:数据完整性验证
*/
function verifyData(
string memory _data,
bytes32 _expectedHash
) public pure returns (bool) {
bytes32 actualHash = keccak256(abi.encodePacked(_data));
return actualHash == _expectedHash;
}
/**
* @notice 用途 2:承诺方案(Commit-Reveal)
*/
mapping(address => bytes32) public commitments;
function commit(bytes32 _commitment) public {
commitments[msg.sender] = _commitment;
}
function reveal(uint256 _secret) public view returns (bool) {
bytes32 hash = keccak256(abi.encodePacked(_secret, msg.sender));
return hash == commitments[msg.sender];
}
/**
* @notice 用途 3:生成唯一标识符
*/
function generateUniqueId(
address _user,
uint256 _nonce
) public pure returns (bytes32) {
return keccak256(abi.encodePacked(_user, _nonce, block.timestamp));
}
/**
* @notice 用途 4:计算函数选择器
*/
function getFunctionSelector(string memory _signature) public pure returns (bytes4) {
// 例如:"transfer(address,uint256)"
return bytes4(keccak256(bytes(_signature)));
}
/**
* @notice 用途 5:计算存储槽位置
*/
function getStorageSlot(
uint256 _mappingSlot,
address _key
) public pure returns (bytes32) {
// mapping 的存储位置 = keccak256(key . slot)
return keccak256(abi.encodePacked(_key, _mappingSlot));
}
/**
* @notice 用途 6:Merkle 树验证
*/
function verifyMerkleProof(
bytes32 _leaf,
bytes32[] memory _proof,
bytes32 _root
) public pure returns (bool) {
bytes32 computedHash = _leaf;
for (uint256 i = 0; i < _proof.length; i++) {
bytes32 proofElement = _proof[i];
if (computedHash < proofElement) {
computedHash = keccak256(abi.encodePacked(computedHash, proofElement));
} else {
computedHash = keccak256(abi.encodePacked(proofElement, computedHash));
}
}
return computedHash == _root;
}
}
/**
* @title HashingBestPractices
* @notice 哈希最佳实践
*/
contract HashingBestPractices {
/**
* @notice ✅ 推荐:使用 abi.encode
* @dev 避免哈希碰撞
*/
function safeHash(string memory _a, string memory _b) public pure returns (bytes32) {
// ✅ 使用 abi.encode:每个参数有明确的边界
return keccak256(abi.encode(_a, _b));
}
/**
* @notice ⚠️ 注意:使用 abi.encodePacked 可能导致碰撞
* @dev 当参数是动态类型时要小心
*/
function potentialCollision(
string memory _a,
string memory _b
) public pure returns (bytes32) {
// ⚠️ abi.encodePacked 可能导致碰撞
// 例如:("aa", "bb") 和 ("a", "abb") 产生相同的哈希
return keccak256(abi.encodePacked(_a, _b));
}
/**
* @notice 演示哈希碰撞
*/
function demonstrateCollision() public pure returns (bool) {
// 这两个调用产生相同的哈希!
bytes32 hash1 = keccak256(abi.encodePacked("aa", "bb"));
bytes32 hash2 = keccak256(abi.encodePacked("a", "abb"));
return hash1 == hash2; // 返回 true!
}
/**
* @notice ✅ 避免碰撞的方法
*/
function avoidCollision(
string memory _a,
string memory _b
) public pure returns (bytes32) {
// 方法 1:使用 abi.encode
return keccak256(abi.encode(_a, _b));
// 方法 2:添加分隔符
// return keccak256(abi.encodePacked(_a, "|", _b));
// 方法 3:包含长度信息
// return keccak256(abi.encodePacked(_a.length, _a, _b.length, _b));
}
}
/**
* @title Keccak256Applications
* @notice Keccak-256 的实际应用
*/
contract Keccak256Applications {
/**
* @notice 应用 1:密码哈希(不推荐直接存储密码)
*/
mapping(address => bytes32) public passwordHashes;
function setPassword(string memory _password) public {
// 注意:实际应用中应该加盐
passwordHashes[msg.sender] = keccak256(abi.encodePacked(_password));
}
function verifyPassword(string memory _password) public view returns (bool) {
return keccak256(abi.encodePacked(_password)) == passwordHashes[msg.sender];
}
/**
* @notice 应用 2:内容寻址存储
*/
mapping(bytes32 => string) public contentStore;
function storeContent(string memory _content) public returns (bytes32) {
bytes32 contentHash = keccak256(abi.encodePacked(_content));
contentStore[contentHash] = _content;
return contentHash;
}
function retrieveContent(bytes32 _hash) public view returns (string memory) {
return contentStore[_hash];
}
/**
* @notice 应用 3:签名验证
*/
function getMessageHash(string memory _message) public pure returns (bytes32) {
return keccak256(abi.encodePacked(_message));
}
function getEthSignedMessageHash(bytes32 _messageHash) public pure returns (bytes32) {
// 以太坊签名消息格式
return keccak256(abi.encodePacked(
"\x19Ethereum Signed Message:\n32",
_messageHash
));
}
}
/**
* @title Keccak256Performance
* @notice Keccak-256 性能考虑
*/
contract Keccak256Performance {
/**
* @notice Gas 成本
* - 基础成本:30 gas
* - 每 32 字节:6 gas
*
* 例如:
* - 哈希 32 字节:~36 gas
* - 哈希 64 字节:~42 gas
* - 哈希 256 字节:~78 gas
*/
/**
* @notice 优化技巧 1:缓存哈希结果
*/
mapping(string => bytes32) private hashCache;
function getCachedHash(string memory _data) public returns (bytes32) {
bytes32 cached = hashCache[_data];
if (cached == bytes32(0)) {
cached = keccak256(abi.encodePacked(_data));
hashCache[_data] = cached;
}
return cached;
}
/**
* @notice 优化技巧 2:预计算常量哈希
*/
bytes32 public constant ADMIN_ROLE = keccak256("ADMIN");
bytes32 public constant USER_ROLE = keccak256("USER");
// 这些哈希在编译时计算,不消耗运行时 gas
}
理论补充:
Keccak-256 vs SHA3-256:
输入:"hello"
Keccak-256(以太坊使用):
0x1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8
SHA3-256(NIST 标准):
0x3338be694f50c5f338814986cdf0686453a888b84f424d792af4b9202398f392
它们不同!
以太坊中的哈希用途:
-
地址生成:
公钥 -> Keccak-256 -> 取后 20 字节 -> 地址 -
交易哈希:
交易数据 -> RLP 编码 -> Keccak-256 -> 交易哈希 -
区块哈希:
区块头 -> RLP 编码 -> Keccak-256 -> 区块哈希 -
状态根:
Merkle Patricia Tree -> Keccak-256 -> 状态根
安全考虑:
- Keccak-256 被认为是密码学安全的
- 没有已知的实际碰撞攻击
- 抗原像攻击和第二原像攻击
- 适合用于密码学应用
性能特点:
- 比 SHA-256 稍快
- 硬件实现效率高
- 适合以太坊的需求
相关问题:
- Q7: 在区块链上创建随机数有哪些挑战?
- Q37: 什么是提交-揭示方案,什么时候会使用它?
- Q44: 自定义错误和带有错误字符串的 require 在 EVM 层面的编码有什么区别?
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐





所有评论(0)