签名,证明事件是在钱包的实际持有者授权的情况下执行的;验证,是通过加密算法对签名有效性的检测。签名并验证通过,是交易和授权的基础。
本文,主要围绕着结构化数据的签名/验证以及合约签名/验证展开。其对应着 EIP712 以及 EIP1271 协议。
1. 数据签名和验证
The set of signable messages is extended from transactions and bytestrings 𝕋 ∪ 𝔹⁸ⁿ to also include structured data 𝕊. The new set of signable messages is thus 𝕋 ∪ 𝔹⁸ⁿ ∪ 𝕊. They are encoded to bytestrings suitable for hashing and signing as follows:
encode(transaction : 𝕋) = RLP_encode(transaction)
encode(message : 𝔹⁸ⁿ) = “\x19Ethereum Signed Message:\n” ‖ len(message) ‖ message where len(message) is the non-zero-padded ascii-decimal encoding of the number of bytes in message.
encode(domainSeparator : 𝔹²⁵⁶, message : 𝕊) = “\x19\x01” ‖ domainSeparator ‖ hashStruct(message) where domainSeparator and hashStruct(message) are defined below:
hashStruct(s : 𝕊) = keccak256(typeHash ‖ encodeData(s)) where typeHash = keccak256(encodeType(typeOf(s)))
domainSeparator = hashStruct(eip712Domain)
引用来源
可以看到,基于非结构化数据 byte 的签名和基于结构化数据的签名,都包含一些特殊前缀,这种设计主要是为了降低不同签名方式的碰撞风险,避免重放攻击。
对于交易的签名和非结构化数据的签名,这里不再赘述,下述重点关注结构化数据签名。
2. EIP712
对结构化数据的签名,将遵循 EIP712 协议。该协议将使得签名过程中数据具有更好的可读性和透明性。
以下,是在 Wyvern 协议中,构建卖单的过程。将调用 MetaMask 进行签名。
1 | const domain = { |
合约中的验证过程如下所示:
1 | /** |
外部账户签名、第三方验证签名的有效性这个过程本身是非常简单的。对于合约账户而言,并不具备有私钥,也无法完成数据的常规签名。
3. EIP1271
EIP1271 就是为合约提供签名和验证的标准。 它的标准接口:
1 | pragma solidity ^0.5.0; |
可知,具体的验证过程,实际上是在 isValidSignature
中完成的,如果验证过程通过,最终应当返回这个魔数 0x1626ba7e
。该方法的实现者,通常是签名的合约本身,即 谁签名谁验证。
接下来,我们找两个合约实例看。
第一个来自于爱死机的合约:
1 | function isSignatureValid(uint256 _category, bytes memory _signature) |
当然,它并不是一个标准的 EIP1271 实现,但是思路上具有一致性,均是通过线下由第三方签名,线上由第三方验证的过程。
用户在线下,(通过 http 访问)向第三方获取到签名,并向合约提交该签名,最终,在合约中验证签名的有效性。
这里,线下签名和签署者和线上签名的验证者,应是同一个账户。
接下来,再看一个来自 Gonisis 中 CompatibilityFallbackHandler
1 | /** |
这里,调用者需要是 Gnosis,将会检查 Gnosis 中是否已经记录了特定消息的签名,如果是,则表示验证通过,否则不通过。