哈希函数(hash function)是一个密码学概念,它可以将任意长度的消息转换为一个固定长度的值,这个值也称作哈希(hash)。
- 单向性:从输入的消息到它的哈希的正向运算简单且唯一确定,而反过来非常难,只能靠暴力枚举。
- 灵敏性:输入的消息改变一点对它的哈希改变很大。
- 高效性:从输入的消息到哈希的运算高效。
- 均一性:每个哈希值被取到的概率应该基本相等。
- 抗碰撞性:
- 弱抗碰撞性:给定一个消息x,找到另一个消息x',使得hash(x) = hash(x')是困难的。
- 强抗碰撞性:找到任意x和x',使得hash(x) = hash(x')是困难的。
- 生成数据唯一标识
- 加密签名
- 安全加密
Keccak256函数是Solidity中最常用的哈希函数,用法非常简单:
哈希 = keccak256(数据);
- sha3由keccak标准化而来,在很多场合下Keccak和SHA3是同义词,但在2015年8月SHA3最终完成标准化时,NIST调整了填充算法。所以SHA3就和keccak计算的结果不一样,这点在实际开发中要注意。
- 以太坊在开发的时候sha3还在标准化中,所以采用了keccak,所以Ethereum和Solidity智能合约代码中的SHA3是指Keccak256,而不是标准的NIST-SHA3,为了避免混淆,直接在合约代码中写成Keccak256是最清晰的。
当我们调用智能合约时,本质上是向目标合约发送了一段calldata,在remix中发送一次交易后,可以在详细信息中看见input即为此次交易的calldata。发送的calldata中前4个字节是selector(函数选择器)。
msg.data是Solidity中的一个全局变量,值为完整的calldata(调用函数时传入的数据)。
其实calldata就是告诉智能合约 ,我要调用哪个函数,以及参数是什么。
- method id定义为函数签名的Keccak哈希后的前4个字节,当selector与method id相匹配时,即表示调用该函数.
- 函数签名:为"函数名(逗号分隔的参数类型)"。
在函数签名中,uint和int要写为uint256和int256。
基础类型的参数有:uint256(uint8, ... , uint256)、bool, address等。在计算method id时,只需要计算bytes4(keccak256("函数名(参数类型1,参数类型2,...)"))。
eg:
// elementary(基础)类型参数selector
// 输入:param1: 1,param2: 0
// elementaryParamSelector(uint256,bool) : 0x3ec37834
function elementaryParamSelector(uint256 param1, bool param2) external returns(bytes4 selectorWithElementaryParam){
emit SelectorEvent(this.elementaryParamSelector.selector);
return bytes4(keccak256("elementaryParamSelector(uint256,bool)"));
}
映射类型参数通常有:contract、enum、struct等。在计算method id时,需要将该类型转化成为ABI类型。 eg:
contract DemoContract {
// empty contract
}
contract Selector{
// Struct User
struct User {
uint256 uid;
bytes name;
}
// Enum School
enum School { SCHOOL1, SCHOOL2, SCHOOL3 }
...
// mapping(映射)类型参数selector
// 输入:demo: 0x9D7f74d0C41E726EC95884E0e97Fa6129e3b5E99, user: [1, "0xa0b1"], count: [1,2,3], mySchool: 1
// mappingParamSelector(address,(uint256,bytes),uint256[],uint8) : 0xe355b0ce
function mappingParamSelector(DemoContract demo, User memory user, uint256[] memory count, School mySchool) external returns(bytes4 selectorWithMappingParam){
emit SelectorEvent(this.mappingParamSelector.selector);
return bytes4(keccak256("mappingParamSelector(address,(uint256,bytes),uint256[],uint8)"));
}
...
}