签名交易:
单纯的转账交易:将(nonce, gasPrice, gasLimit, to, value, data:空, v, r, s)发往节点。注意,这里data是空的。(此处需要EIP-155基础)
部署合约交易:将(nonce, gasPrice, gasLimit, to:空, value, data:合约创建字节码, v, r, s)发往节点。注意,to是空的,data是合约创建字节码,节点看到to是空的就知道这是部署合约交易。
调用合约函数交易:将(nonce, gasPrice, gasLimit, to, value, data:selector+函数参数, v, r, s)发往节点。注意,data是selector+函数参数,例如bytes4(keccak256(bytes(“foo(uint256,address,string,uint256[2])”))), x, addr, name, array。
其中,v,r,s是已签名的“消息”,可以理解成前六个参数是消息明文,后三个参数是签名后的消息(密文)。将【明文,密文】组成的交易签名后即可成为签名“交易”发送给节点。
故这个过程中需要2次签名,一次是消息签名,一次是交易签名。
先说消息签名:
1)0x1901 以太坊消息(EIP-191签名标准)
简单点来说就是添加了"\x19Ethereum Signed Message:\n"这个字符串的签名,添加这个字符串只是单纯的为了表明这是以太坊的签名
签名逻辑:
encodepacked(参数1,参数2,参数3,…)
-> Hash1=keccak256(encodepacked(参数1,参数2,参数3,…)
-> keccak256(abi.encodePacked(“\x19Ethereum Signed Message:\n32”, Hash1))
-> 加入私钥进行运算得到"消息签名”。
2)0x1900
EIP-191提出了在签名中加入validator参数(一般为合约自身的address参数),以防止重放攻击的手法。实际应用中使用EIP-712更多。
3)0x1945(EIP-712)
EIP-712的消息由三部分组成(712的结构式):
encode(‘\x19\x01’, domainSeparator, message),然后进行哈希计算
其中domainSeparator, message需要其“结构类型”的keccak256(哈希)及其“实际值”的keccak256,之后进行encode编码后再次进行keccak256。
(注:domainSeparator(域名分隔符)是在construct的时候就被定义好了的,声明我这个合约是要做什么的)
展开说明domainSqparator:
domainSeparator由两部分组成,
第一部分为对结构体的keccak256——encodeType,
第二部分为结构体的具体实现——encodeData;
①encodeType:
domainSeparator结构体如下所示:(一般来说salt(随机数)会省略)
struct EIP712Domain{
string name, //用户可读的域名,如DAPP的名字
// 目前签名的域的版本号 uint256 chainId, // EIP-155中定义的chain ID,如以太坊主网为1
string version,
address verifyingContract, // 用于验证签名的合约地址
bytes32 salt // 随机数
}
encodeType = keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)')
②encodeData则是对结构体具体数值的编码:
keccak256(bytes(name)), keccak256(bytes(‘1’)), 1, address(this(可更改)) (salt可不使用)
(注:对于string,bytes类型需要进行keccak256编码统一)
即 encodeData=keccak256(bytes(name), keccak256(bytes('1')), 1, address(this))
综上↓:
domainSqparator = keccak256( // domainSqparator 的定义可以看作是常量,因为合约创建之初就给定了
abi.encode(keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'),
keccak256(bytes(name)),
keccak256(bytes('1')),
chainId, address(this)
)
或者可以理解为:
domainSqparator = keccak256( encodeType , encodeData)
//***********************************************************************
//********************* 分割线*******************************************
//***********************************************************************
展开说明message
MESSAGE由两部分组成,
第一部分为对结构体类型的哈希——TYPEHASH,
第二部分为结构体的具体实现——message;
struct message {
address owner,
address spender,
uint256 value,
uint256 nonce,
uint256 deadline
}
① TYPEHASH = keccak256(‘message(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)’)
② message = owner, spender, value, nonces[owner]++, deadline
综上↓
MESSAGE=keccak256(abi.encode(PERMIT_TYPEHASH, message))
(注意:domainSeparator,message都经过encode编码(按序拼接),keccak256处理)
//***********************************************************************
//********************** 分割线********************************************
//***********************************************************************
将上述拼接在一起再次进行编码、哈希,成为符合EIP-712标准的消息:
digest = keccak256( abi.encodePacked( ‘\x19\x01’, domainSqparator , MESSAGE )
此时的消息(digest)成为符合EIP-712标准的待签名消息,即通用签名方法中的data。
(注:待签名消息!=待签名交易 将已签名消息作为data打包进交易中给发送者签名,才能成为一个签名交易)
//***********************************************************************
//********************** 分割线********************************************
//***********************************************************************
链下签名:
async function signTypedData(signer) {
// 将domain的类型与赋值描述清楚
const domain = {
name: "Uniswap V2", version: "1", chainId: 1, verifyingContract: "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC", }
// 给出数据类型与名称
const types = {
Permit: [
{
name: "owner", type: "address" },
{
name: "spender", type: "address" },
{
name: "value", type: "uint256" },
{
name: "nonce", type: "uint256" },
{
name: "deadline", type: "uint256" }, ], } // The data to sign
// 输入确定的数据数值
const value = {
owner: xx, spender: xx, value: xx, nonce: xx, deadline: xx, }
// 签名
const signature = await signer._signTypedData(domain, types, value) return signature }
*参考文献:
https://eips.ethereum.org/EIPS/eip-712#rationale-for-typehash
https://mirror.xyz/xyyme.eth/cJX3zqiiUg2dxB1nmbXbDcQ1DSdajHP5iNgBc6wEZz4
https://learnblockchain.cn/article/5012#%E7%AD%BE%E5%90%8D%E6%B6%88%E6%81%AF%EF%BC%88%20=presigned%20message%20=%20%E9%A2%84%E7%AD%BE%E5%90%8D%EF%BC%89
https://learnblockchain.cn/article/2753#%E5%85%83%E4%BA%A4%E6%98%93
https://mirror.xyz/adshao.eth/qmzSfrOB8s6_-s1AsflYNqEkTynShdpBE0EliqjGC1U