EIP712EIP712是以太坊的一次改进提案,旨在将签名的过程从链上转移至链下,节省Gas费。EIP712的完整细节可以参考EIP-712:Typedstructureddatahashingandsigning为什么要用EIP712链下消息签名,链上验证的形式,可以省去多
EIP712是以太坊的一次改进提案,旨在将签名的过程从链上转移至链下,节省Gas费。
EIP712的完整细节可以参考
EIP-712: Typed structured data hashing and signing
当你需要实现不同的功能的时候,你会需要用不同的结构体存储数据。在签名时你需要一个唯一可以体现你的身份信息的签名,那么你理应有一个自定义的结构体数据。
假设我们需要创建一个邮件的链下签名,我们应该把发送方的地址,接受者的地址,邮件内容
作为一个结构体,在构造自己的自定义的逻辑时,可以适当的根据你的功能需求拓展你的结构体。
struct Mail {
address from;
address to;
string contents;
}
hashStruct(s : 𝕊) = keccak256(typeHash ‖ encodeData(s))
其中 typeHash = keccak256(encodeType(typeOf(s)))
注意: typeHash
是给定结构类型的常量,不需要运行时计算。
假设是我们第一步示例中的Email
结构体,其计算方法如下
bytes32 typeHash = keccak256("Mail(address from,address to,string contents");
如果结构类型引用其他结构类型(而这些结构类型又引用更多结构类型),则收集引用的结构类型集,按名称排序并附加到编码中。一个示例编码是 Transaction(Person from,Person to,Asset tx)Asset(address token,uint256 amount)Person(address wallet,string name)
。
这一步是往你刚才定义的struct中添加数据作为你的签名内容,形成一个独一无二的签名用作链上验证。
bytes32 structHash = keccak256(
abi.encode(
typeHash,
from,
to,
keccak256(contents)
)
高效的实现方法
function hashStruct(Mail memory mail) pure returns (bytes32 hash) {
// Compute sub-hashes
bytes32 typeHash = MAIL_TYPEHASH;
bytes32 contentsHash = keccak256(mail.contents);
assembly {
// Back up select memory
let temp1 := mload(sub(mail, 32))
let temp2 := mload(add(mail, 128))
// Write typeHash and sub-hashes
mstore(sub(mail, 32), typeHash)
mstore(add(mail, 64), contentsHash)
// Compute hash
hash := keccak256(sub(mail, 32), 128)
// Restore memory
mstore(sub(mail, 32), temp1)
mstore(add(mail, 64), temp2)
}
}
domainSeparator = hashStruct(eip712Domain)
其中 eip712Domain
的类型是名为 EIP712Domain
的结构,具有以下一个或多个字段。协议设计者只需要包含对其签名域有意义的字段。未使用的字段被排除在结构类型之外。
string name
签名域的用户可读名称,即 DApp 或协议的名称。string version
签名域的当前主要版本。不同版本的签名不兼容。uint256 chainId
EIP-155 链 ID。如果用户代理与当前活动链不匹配,则应拒绝签名。address verifyingContract
将验证签名的合约地址。用户代理可以进行特定于合同的网络钓鱼预防。bytes32 salt
协议的消歧义盐。这可以用作最后手段的域分隔符。到这一步,你已经把你要调用的合约的信息独一无二地表示出来了,同时你也把你要对合约的函数所提交的内容也用签名的形式独一无二的表现出来了,你可以用上述的信息编码,生成的你签名摘要。
bytes32 digest = keccak256(
abi.encodePacked(
"\\x19\\x01",
domainSeparator,
structHash
)
);
更省Gas的写法可以参考openzepplin的实现
function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32 digest) {
/// @solidity memory-safe-assembly
assembly {
let ptr := mload(0x40)
mstore(ptr, hex"19_01")
mstore(add(ptr, 0x02), domainSeparator)
mstore(add(ptr, 0x22), structHash)
digest := keccak256(ptr, 0x42)
}
}
在链上,合约可以获得上述的数据生成一个与你相同的摘要Hash,接下来只要通过你传入的密钥信息来还原出签名者信息,对比签名者信息与发送者地址是否一致,就能确认这些信息是否由你本人提交。加密算法为ECDSA椭圆曲线加密。
address signer = ECDSA.recover(hash, v, r, s);
if (signer != owner) {
revert ERC2612InvalidSigner(signer, owner);
}
在链下,你的v,r,s由你的私钥签名摘要信息后生成
(uint8 v, bytes32 r, bytes32 s) = vm.sign(ownerPrivateKey, digest);
var domain = [
{ name: "name", type: "string" },
{ name: "version", type: "string" },
{ name: "chainId", type: "uint256" },
{ name: "verifyingContract", type: "address" },
{ name: "salt", type: "bytes32" }
];
var mail = [
{ name: "from", type: "address" },
{ name: "to", type: "address" },
{ name: "contents", type: "string" }
];
const domainData = {
name: "app",
version: "1",
chainId: 3,
verifyingContract: cardExchangeContract,
salt: "0xa222082684812afae4e093416fff16bc218b569abe4db590b6a058e1f2c1cd3e"
};
var message = {
from: [0xzch....fsha],
to: [0xzsdas....rwjfs],
contents: [this is content]
};
var data = JSON.stringify({
types: {
EIP712Domain: domain,
Mail: mail,
},
domain: domainData,
primaryType: "Mail",
message: message
});
window.web3.currentProvider.sendAsync({
method: "eth_signTypedData_v4",
params: [address, data],
from: address
}, function(error, result) {
if (error) {
errorCallback();
} else {
const signature = result.result.substring(2);
const r = "0x" + signature.substring(0, 64);
const s = "0x" + signature.substring(64, 128);
const v = parseInt(signature.substring(128, 130), 16);
successCallback(signature, r, s, v);
}
});
如果你需要使用EIP712,你需要准备三个数据,DomainSeparator
,TypedDataHash
,你的密钥v,r,s
该标准仅涉及签名消息和验证签名。在许多实际应用中,签名消息用于授权操作,例如令牌交换。实施者确保应用程序在两次看到相同的签名消息时行为正确,这一点非常重要。例如,重复的消息应该被拒绝或者授权的操作应该是幂等的。其实现方式取决于应用程序,超出了本标准的范围。
可靠地广播签名的机制是特定于应用程序的,超出了本标准的范围。当签名被广播到区块链以在合约中使用时,应用程序必须能够抵御抢先交易攻击。在这种攻击中,攻击者拦截签名并将其在原始预期用途发生之前提交给合约。当攻击者首先提交签名时,应用程序应正确运行,例如拒绝签名或简单地产生与签名者预期完全相同的效果。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!